diff --git a/Gopkg.lock b/Gopkg.lock index 4fbce4f..e793a3c 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -4,8 +4,7 @@ [[projects]] name = "github.com/Bit-Nation/protobuffers" packages = ["."] - revision = "b60cd16e6d11da8b88a4f1732fc916e3458952a4" - version = "1.1.0" + revision = "b104bab4e16e94443febee60f003a2e5bacd6e03" [[projects]] branch = "master" @@ -25,6 +24,12 @@ packages = ["."] revision = "d3a63a5752ecf3fbc06bd97365da752111c263df" +[[projects]] + branch = "master" + name = "github.com/NaySoftware/go-fcm" + packages = ["."] + revision = "28fff9381d17f35619309c7a5ada41d26030d976" + [[projects]] branch = "master" name = "github.com/agl/ed25519" @@ -35,12 +40,50 @@ ] revision = "5312a61534124124185d41f09206b9fef1d88403" +[[projects]] + branch = "master" + name = "github.com/aristanetworks/goarista" + packages = ["monotime"] + revision = "ff33da284e760fcdb03c33d37a719e5ed30ba844" + +[[projects]] + name = "github.com/asdine/storm" + packages = [ + ".", + "codec", + "codec/json", + "index", + "internal", + "q" + ] + revision = "5a6fe65e3993f63951c8e3f64c812536c9e23595" + version = "v2.1.2" + +[[projects]] + branch = "master" + name = "github.com/beorn7/perks" + packages = ["quantile"] + revision = "3a771d992973f24aa725d07868b467d1ddfceafb" + [[projects]] branch = "master" name = "github.com/btcsuite/btcd" - packages = ["btcec"] + packages = [ + "btcec", + "chaincfg", + "chaincfg/chainhash", + "wire" + ] revision = "f673a4b563b57b9a95832545c878669a7fa801d9" +[[projects]] + name = "github.com/btcsuite/btcutil" + packages = [ + ".", + "base58" + ] + revision = "dcd4997b0664bcfd6ef48e4ae9da8396e08b1cd9" + [[projects]] name = "github.com/coreos/bbolt" packages = ["."] @@ -59,19 +102,87 @@ revision = "346938d642f2ec3594ed81d874461961cd0faa76" version = "v1.1.0" +[[projects]] + name = "github.com/deckarep/golang-set" + packages = ["."] + revision = "1d4478f51bed434f1dadf96dcd9b43aabac66795" + version = "v1.7" + +[[projects]] + branch = "master" + name = "github.com/edsrzf/mmap-go" + packages = ["."] + revision = "0bce6a6887123b67a60366d2c9fe2dfb74289d2e" + [[projects]] name = "github.com/ethereum/go-ethereum" packages = [ + ".", + "accounts", + "accounts/abi", + "accounts/abi/bind", + "accounts/keystore", + "accounts/usbwallet", + "accounts/usbwallet/internal/trezor", "common", + "common/bitutil", + "common/fdlimit", "common/hexutil", "common/math", + "common/mclock", + "common/prque", + "consensus", + "consensus/clique", + "consensus/ethash", + "consensus/misc", + "core", + "core/bloombits", + "core/rawdb", + "core/state", + "core/types", + "core/vm", "crypto", + "crypto/bn256", + "crypto/bn256/cloudflare", + "crypto/bn256/google", + "crypto/ecies", "crypto/secp256k1", "crypto/sha3", - "rlp" + "eth", + "eth/downloader", + "eth/fetcher", + "eth/filters", + "eth/gasprice", + "eth/tracers", + "eth/tracers/internal/tracers", + "ethclient", + "ethdb", + "event", + "internal/debug", + "internal/ethapi", + "les", + "les/flowcontrol", + "light", + "log", + "log/term", + "metrics", + "metrics/exp", + "miner", + "node", + "p2p", + "p2p/discover", + "p2p/discv5", + "p2p/nat", + "p2p/netutil", + "params", + "rlp", + "rpc", + "trie", + "whisper/mailserver", + "whisper/whisperv6" ] - revision = "37685930d953bcbe023f9bc65b135a8d8b8f1488" - version = "v1.8.12" + revision = "89451f7c382ad2185987ee369f16416f89c28a7d" + version = "v1.8.15" [[projects]] name = "github.com/fd/go-nat" @@ -79,6 +190,36 @@ revision = "bad65a492f32121a87197f4a085905c35e2a367e" version = "v1.0.0" +[[projects]] + branch = "master" + name = "github.com/fjl/memsize" + packages = [ + ".", + "memsizeui" + ] + revision = "f6d5545993d68e9e42df43eb57958f898dea3623" + +[[projects]] + name = "github.com/go-playground/locales" + packages = [ + ".", + "currency" + ] + revision = "f63010822830b6fe52288ee52d5a1151088ce039" + version = "v0.12.1" + +[[projects]] + name = "github.com/go-playground/universal-translator" + packages = ["."] + revision = "b32fa301c9fe55953584134cb6853a13c87ec0a1" + version = "v0.16.0" + +[[projects]] + name = "github.com/go-stack/stack" + packages = ["."] + revision = "2fee6af1a9795aafbe0253a0cfbdf668e1fb8a9a" + version = "v1.8.0" + [[projects]] name = "github.com/gogo/protobuf" packages = [ @@ -88,12 +229,33 @@ revision = "636bf0302bc95575d69441b25a2603156ffdddf1" version = "v1.1.1" +[[projects]] + name = "github.com/golang/mock" + packages = ["gomock"] + revision = "c34cdb4725f4c3844d095133c6e40e448b86589b" + version = "v1.1.1" + [[projects]] name = "github.com/golang/protobuf" - packages = ["proto"] + packages = [ + "proto", + "protoc-gen-go/descriptor" + ] revision = "b4deda0973fb4c70b50d226b1af49f3da59f5265" version = "v1.1.0" +[[projects]] + branch = "master" + name = "github.com/golang/snappy" + packages = ["."] + revision = "2e65f85255dbc3072edf28d6b5b8efc472979f5a" + +[[projects]] + name = "github.com/google/uuid" + packages = ["."] + revision = "d460ce9f8df2e77fb1ba55ca87fafed96c607494" + version = "v1.0.0" + [[projects]] name = "github.com/gorilla/context" packages = ["."] @@ -124,6 +286,15 @@ packages = ["."] revision = "80a92cca79a8041496ccc9dd773fcb52a57ec6f9" +[[projects]] + name = "github.com/hashicorp/golang-lru" + packages = [ + ".", + "simplelru" + ] + revision = "20f1fb78b0740ba8c3cb143a61e86ba5c8669768" + version = "v0.5.0" + [[projects]] branch = "master" name = "github.com/huin/goupnp" @@ -155,6 +326,12 @@ ] revision = "75cbd19cc9af4467702602311230785df263b671" +[[projects]] + name = "github.com/iris-contrib/go.uuid" + packages = ["."] + revision = "36e9d2ebbde5e3f13ab2e25625fd453271d6522e" + version = "v2.0.0" + [[projects]] name = "github.com/jackpal/gateway" packages = ["."] @@ -190,6 +367,18 @@ ] revision = "b497e2f366b8624394fb2e89c10ab607bebdde0b" +[[projects]] + branch = "master" + name = "github.com/karalabe/hid" + packages = ["."] + revision = "2b4488a37358b7283de4f9622553e85ebbe73125" + +[[projects]] + name = "github.com/kataras/iris" + packages = ["core/errors"] + revision = "217d9feadc0fc5b317826a901065e0c4c27d3cad" + version = "10.7.0" + [[projects]] name = "github.com/libp2p/go-addr-util" packages = ["."] @@ -404,6 +593,12 @@ revision = "0360b2af4f38e8d38c7fce2a9f4e702702d73a39" version = "v0.0.3" +[[projects]] + name = "github.com/matttproud/golang_protobuf_extensions" + packages = ["pbutil"] + revision = "c12348ce28de40eed0136aa2b644d0ee0650e56c" + version = "v1.0.1" + [[projects]] branch = "master" name = "github.com/minio/sha256-simd" @@ -468,6 +663,12 @@ revision = "1949ddbfd147afd4d964a9f00b24eb291e0e7c38" version = "v1.0.2" +[[projects]] + name = "github.com/pborman/uuid" + packages = ["."] + revision = "adf5a7427709b9deb95d29d3fa8a2bf9cfd388f1" + version = "v1.2" + [[projects]] name = "github.com/pmezard/go-difflib" packages = ["difflib"] @@ -475,6 +676,52 @@ version = "v1.0.0" [[projects]] + name = "github.com/prometheus/client_golang" + packages = ["prometheus"] + revision = "c5b7fccd204277076155f10851dad72b76a49317" + version = "v0.8.0" + +[[projects]] + branch = "master" + name = "github.com/prometheus/client_model" + packages = ["go"] + revision = "5c3871d89910bfb32f5fcab2aa4b9ec68e65a99f" + +[[projects]] + branch = "master" + name = "github.com/prometheus/common" + packages = [ + "expfmt", + "internal/bitbucket.org/ww/goautoneg", + "model" + ] + revision = "c7de2306084e37d54b8be01f3541a8464345e9a5" + +[[projects]] + branch = "master" + name = "github.com/prometheus/procfs" + packages = [ + ".", + "internal/util", + "nfs", + "xfs" + ] + revision = "418d78d0b9a7b7de3a6bbc8a23def624cc977bb2" + +[[projects]] + name = "github.com/prometheus/prometheus" + packages = ["util/flock"] + revision = "85f23d82a045d103ea7f3c89a91fba4a93e6367a" + version = "v2.1.0" + +[[projects]] + name = "github.com/rjeczalik/notify" + packages = ["."] + revision = "0f065fa99b48b842c3fd3e2c8b194c6f2b69f6b8" + version = "v0.9.1" + +[[projects]] + branch = "master" name = "github.com/robertkrimen/otto" packages = [ ".", @@ -487,12 +734,55 @@ ] revision = "15f95af6e78dcd2030d8195a138bd88d4f403546" +[[projects]] + name = "github.com/rs/cors" + packages = ["."] + revision = "3fb1b69b103a84de38a19c3c6ec073dd6caa4d3f" + version = "v1.5.0" + [[projects]] name = "github.com/satori/go.uuid" packages = ["."] revision = "f58768cc1a7a7e77a3bd49e98cdd21419399b6a3" version = "v1.2.0" +[[projects]] + name = "github.com/status-im/go-web3js" + packages = ["."] + revision = "a136c82b1e1f30b5b24118eeff1caa71924e4e3a" + version = "0.20.1" + +[[projects]] + branch = "experimental/fix-logging-old" + name = "github.com/status-im/status-go" + packages = [ + "extkeys", + "geth/account", + "geth/api", + "geth/db", + "geth/jail", + "geth/jail/console", + "geth/jail/internal/fetch", + "geth/jail/internal/loop", + "geth/jail/internal/loop/looptask", + "geth/jail/internal/promise", + "geth/jail/internal/timers", + "geth/jail/internal/vm", + "geth/mailservice", + "geth/node", + "geth/notifications/push/fcm", + "geth/params", + "geth/peers", + "geth/rpc", + "geth/signal", + "geth/transactions", + "metrics/whisper", + "shhext", + "sign", + "static" + ] + revision = "4f8a957fbb40b74b90a0ebe2c7b80c55c0dc16a6" + [[projects]] name = "github.com/stretchr/testify" packages = [ @@ -502,6 +792,25 @@ revision = "f35b8ab0b5a2cef36673838d662e249dd9c94686" version = "v1.2.2" +[[projects]] + branch = "master" + name = "github.com/syndtr/goleveldb" + packages = [ + "leveldb", + "leveldb/cache", + "leveldb/comparer", + "leveldb/errors", + "leveldb/filter", + "leveldb/iterator", + "leveldb/journal", + "leveldb/memdb", + "leveldb/opt", + "leveldb/storage", + "leveldb/table", + "leveldb/util" + ] + revision = "ae2bd5eed72d46b28834ec3f60db3a3ebedd8dbd" + [[projects]] name = "github.com/tiabc/doubleratchet" packages = ["."] @@ -577,6 +886,12 @@ packages = ["."] revision = "cb29a700b01dc3c2fdd743c00cf54685056bb62a" +[[projects]] + name = "go.etcd.io/bbolt" + packages = ["."] + revision = "583e8937c61f1af6513608ccc75c97b6abdf4ff9" + version = "v1.3.0" + [[projects]] branch = "master" name = "golang.org/x/crypto" @@ -600,14 +915,22 @@ "context", "html", "html/atom", - "html/charset" + "html/charset", + "websocket" ] revision = "d0887baf81f4598189d4e12a37c6da86f0bba4d0" +[[projects]] + branch = "master" + name = "golang.org/x/sync" + packages = ["syncmap"] + revision = "1d60e4601c6fd243af51cc01ddf169918a5407ca" + [[projects]] branch = "master" name = "golang.org/x/sys" packages = [ + "cpu", "unix", "windows" ] @@ -628,15 +951,52 @@ "encoding/unicode", "internal/gen", "internal/tag", + "internal/triegen", + "internal/ucd", "internal/utf8internal", "language", "runes", "transform", - "unicode/cldr" + "unicode/cldr", + "unicode/norm" ] revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0" version = "v0.3.0" +[[projects]] + branch = "master" + name = "golang.org/x/tools" + packages = [ + "go/ast/astutil", + "imports", + "internal/fastwalk" + ] + revision = "677d2ff680c188ddb7dcd2bfa6bc7d3f2f2f75b2" + +[[projects]] + name = "gopkg.in/go-playground/validator.v9" + packages = ["."] + revision = "e69e9a28bb62b977fdc58d051f1bb477b7cbe486" + version = "v9.21.0" + +[[projects]] + branch = "v2" + name = "gopkg.in/karalabe/cookiejar.v2" + packages = ["collections/prque"] + revision = "8dcd6a7f4951f6ff3ee9cbb919a06d8925822e57" + +[[projects]] + branch = "v2" + name = "gopkg.in/natefinch/npipe.v2" + packages = ["."] + revision = "c1b8fa8bdccecb0b8db834ee0b92fdbcfa606dd6" + +[[projects]] + branch = "v3" + name = "gopkg.in/olebedev/go-duktape.v3" + packages = ["."] + revision = "d53328019b21fe1987c97f368f98c072c8f44455" + [[projects]] name = "gopkg.in/sourcemap.v1" packages = [ @@ -646,9 +1006,15 @@ revision = "6e83acea0053641eff084973fee085f0c193c61a" version = "v1.0.5" +[[projects]] + name = "gopkg.in/urfave/cli.v1" + packages = ["."] + revision = "cfb38830724cc34fedffe9a2a29fb54fa9169cd1" + version = "v1.20.0" + [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "74990307dbe7b3f0288ac0bdaa0a720a17eb375c2486a238482be0e36d92bb57" + inputs-digest = "259be0c4cd8a1b7192860ea4411e31bc27b7226a00882ec894176a86fcc76c74" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index dfcb186..5ac7e35 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -34,7 +34,7 @@ [[constraint]] name = "github.com/ethereum/go-ethereum" - version = "1.8.11" + version = "1.8.15" [[constraint]] branch = "master" @@ -138,4 +138,4 @@ [[constraint]] name = "github.com/Bit-Nation/protobuffers" - version = "1.1.0" + revision = "b104bab4e16e94443febee60f003a2e5bacd6e03" diff --git a/api/dapp.go b/api/dapp.go index 5f48b18..aa03883 100644 --- a/api/dapp.go +++ b/api/dapp.go @@ -73,9 +73,9 @@ func (a *DAppApi) SendEthereumTransaction(value, to, data string) (string, error "v": ethTx.V, "r": ethTx.R, "s": ethTx.S, - "chainId": ethTx.ChainID, "from": ethTx.From, "hash": ethTx.Hash, + "chainId": ethTx.ChainID, } raw, err := json.Marshal(objTx) diff --git a/chat.go b/chat.go index 22f56e7..3d5ecf0 100644 --- a/chat.go +++ b/chat.go @@ -5,28 +5,90 @@ import ( "encoding/json" "errors" "strconv" + + ed25519 "golang.org/x/crypto/ed25519" ) -func SendMessage(partner, message string) error { +func SendMessage(chatID int, message string) error { // make sure panthalassa has been started if panthalassaInstance == nil { return errors.New("you have to start panthalassa first") } - // partner public key - partnerPub, err := hex.DecodeString(partner) - if err != nil { + // persist message + return panthalassaInstance.chat.SaveMessage(chatID, []byte(message)) + +} + +func AddUsersToGroupChat(users string, chatID int) error { + + // make sure panthalassa has been started + if panthalassaInstance == nil { + return errors.New("you have to start panthalassa first") + } + + // json unmarshal partners + rawPartners := []string{} + if err := json.Unmarshal([]byte(users), &rawPartners); err != nil { return err } - // make sure public key has the right length - if len(partnerPub) != 32 { - return errors.New("partner must have a length of 32 bytes") + // unmarshal hex partners + partners := []ed25519.PublicKey{} + for _, hexPartner := range rawPartners { + rawPartner, err := hex.DecodeString(hexPartner) + if err != nil { + return err + } + partners = append(partners, rawPartner) } - // persist private message - return panthalassaInstance.chat.SavePrivateMessage(partnerPub, []byte(message)) + return panthalassaInstance.chat.AddUserToGroupChat(partners, chatID) + +} + +func CreatePrivateChat(partnerStr string) (int, error) { + + // make sure panthalassa has been started + if panthalassaInstance == nil { + return 0, errors.New("you have to start panthalassa first") + } + + partner, err := hex.DecodeString(partnerStr) + if err != nil { + return 0, err + } + + return panthalassaInstance.chatDB.CreateChat(partner) + +} + +// return chatID +func CreateGroupChat(users string, name string) (int, error) { + + // make sure panthalassa has been started + if panthalassaInstance == nil { + return 0, errors.New("you have to start panthalassa first") + } + + // json unmarshal partners + rawPartners := []string{} + if err := json.Unmarshal([]byte(users), &rawPartners); err != nil { + return 0, err + } + + // unmarshal hex partners + partners := []ed25519.PublicKey{} + for _, hexPartner := range rawPartners { + rawPartner, err := hex.DecodeString(hexPartner) + if err != nil { + return 0, err + } + partners = append(partners, rawPartner) + } + + return panthalassaInstance.chat.CreateGroupChat(partners, name) } @@ -45,8 +107,11 @@ func AllChats() (string, error) { chatsRep := []map[string]interface{}{} for _, chat := range chats { chatsRep = append(chatsRep, map[string]interface{}{ - "chat": chat.Partner, + "chat_id": chat.ID, + "chat_partner": chat.Partner, "unread_messages": chat.UnreadMessages, + "group_chat_name": chat.GroupChatName, + "partners": chat.Partners, }) } @@ -58,7 +123,7 @@ func AllChats() (string, error) { return string(chatList), nil } -func Messages(partner string, startStr string, amount int) (string, error) { +func Messages(chatID int, startStr string, amount int) (string, error) { // unmarshal start start, err := strconv.ParseInt(startStr, 10, 64) @@ -71,19 +136,18 @@ func Messages(partner string, startStr string, amount int) (string, error) { return "", errors.New("you have to start panthalassa first") } - // partner public key - partnerPub, err := hex.DecodeString(partner) + // fetch chat + chat, err := panthalassaInstance.chatDB.GetChat(chatID) if err != nil { return "", err } - // make sure public key has the right length - if len(partnerPub) != 32 { - return "", errors.New("partner must have a length of 32 bytes") + if chat == nil { + return "", errors.New("chat doesn't exit") } // database messages - databaseMessages, err := panthalassaInstance.chat.Messages(partnerPub, int64(start), uint(amount)) + databaseMessages, err := chat.Messages(start, uint(amount)) if err != nil { return "", err } @@ -108,6 +172,7 @@ func Messages(partner string, startStr string, amount int) (string, error) { "created_at": msg.CreatedAt, "received": msg.Received, "dapp": dapp, + "sender": msg.Sender, }) } @@ -121,21 +186,13 @@ func Messages(partner string, startStr string, amount int) (string, error) { } -func MarkMessagesAsRead(partner string) error { +func MarkMessagesAsRead(chatID int) error { // make sure panthalassa has been started if panthalassaInstance == nil { return errors.New("you have to start panthalassa first") } - partnerByte, err := hex.DecodeString(partner) - if err != nil { - return err - } - if len(partnerByte) != 32 { - return errors.New("partner must have length of 32 bytes") - } - - return panthalassaInstance.chat.MarkMessagesAsRead(partnerByte) + return panthalassaInstance.chat.MarkMessagesAsRead(chatID) } diff --git a/chat/chat.go b/chat/chat.go index 216bc97..0221be7 100644 --- a/chat/chat.go +++ b/chat/chat.go @@ -46,7 +46,7 @@ func (c *Chat) AllChats() ([]db.Chat, error) { } func (c *Chat) Messages(partner ed25519.PublicKey, start int64, amount uint) ([]*db.Message, error) { - chat, err := c.chatStorage.GetChat(partner) + chat, err := c.chatStorage.GetChatByPartner(partner) if err != nil { return nil, err } diff --git a/chat/group_chat.go b/chat/group_chat.go new file mode 100644 index 0000000..1627bd8 --- /dev/null +++ b/chat/group_chat.go @@ -0,0 +1,145 @@ +package chat + +import ( + "encoding/hex" + "errors" + "time" + + db "github.com/Bit-Nation/panthalassa/db" + uid "github.com/satori/go.uuid" + ed25519 "golang.org/x/crypto/ed25519" +) + +func (c *Chat) AddUserToGroupChat(partners []ed25519.PublicKey, chatID int) error { + + chat, err := c.chatStorage.GetChat(chatID) + if err != nil { + return err + } + + if chat == nil { + return errors.New("couldn't find chat") + } + + if !chat.IsGroupChat() { + return errors.New("chat must be a group chat") + } + + // update chat partners + if err := chat.AddChatPartners(partners); err != nil { + return err + } + + // fetch chat + for _, partner := range partners { + + // fetch chat + partnerChat, err := c.chatStorage.GetChatByPartner(partner) + if err != nil { + return err + } + if partnerChat == nil { + _, err = c.chatStorage.CreateChat(partner) + if err != nil { + return err + } + } + partnerChat, err = c.chatStorage.GetChatByPartner(partner) + if err != nil { + return err + } + if partnerChat == nil { + return errors.New("chat with partner should exist at this point in time") + } + + // persist message + msg := db.Message{ + AddUserToChat: &db.AddUserToChat{ + Users: partners, + ChatID: chat.GroupChatRemoteID, + }, + } + if err := partnerChat.PersistMessage(msg); err != nil { + return err + } + + } + + return nil + +} + +func (c *Chat) CreateGroupChat(partners []ed25519.PublicKey, name string) (int, error) { + + // create chat + chatID, err := c.chatStorage.CreateGroupChat(partners, name) + if err != nil { + return 0, err + } + + // fetch group chat + groupChat, err := c.chatStorage.GetChat(chatID) + if err != nil { + return 0, err + } + + // sender + idKeyStr, err := c.km.IdentityPublicKey() + if err != nil { + return 0, err + } + + idKey, err := hex.DecodeString(idKeyStr) + if err != nil { + return 0, err + } + + // send message to our group chat partners + for _, partner := range partners { + + // fetch chat + partnerChat, err := c.chatStorage.GetChatByPartner(partner) + if err != nil { + return 0, err + } + if partnerChat == nil { + _, err = c.chatStorage.CreateChat(partner) + if err != nil { + return 0, err + } + } + partnerChat, err = c.chatStorage.GetChatByPartner(partner) + if err != nil { + return 0, err + } + if partnerChat == nil { + return 0, errors.New("chat with partner should exist at this point in time") + } + + msgID, err := uid.NewV4() + if err != nil { + return 0, err + } + + // persist message + msg := db.Message{ + AddUserToChat: &db.AddUserToChat{ + Users: partners, + ChatID: groupChat.GroupChatRemoteID, + }, + Version: 1, + CreatedAt: time.Now().UnixNano(), + Status: db.StatusPersisted, + Sender: idKey, + GroupChatID: groupChat.GroupChatRemoteID, + ID: msgID.String(), + } + if err := partnerChat.PersistMessage(msg); err != nil { + return 0, err + } + + } + + return chatID, nil + +} diff --git a/chat/handler.go b/chat/handler.go index 362681f..12e0b4a 100644 --- a/chat/handler.go +++ b/chat/handler.go @@ -2,12 +2,10 @@ package chat import ( "crypto/rand" - "encoding/hex" "encoding/json" "errors" - "sync" - "strconv" + "sync" preKey "github.com/Bit-Nation/panthalassa/chat/prekey" db "github.com/Bit-Nation/panthalassa/db" @@ -118,7 +116,7 @@ func (c *Chat) handlePersistedMessage(e db.MessagePersistedEvent) { err := c.queue.AddJob(queue.Job{ Type: "MESSAGE:SUBMIT", Data: map[string]interface{}{ - "partner": e.Chat.Partner, + "chat_id": e.Chat.ID, "db_message_id": e.Message.UniqueMsgID, }, }) @@ -142,9 +140,10 @@ func (c *Chat) handlePersistedMessage(e db.MessagePersistedEvent) { "db_id": strconv.FormatInt(e.Message.UniqueMsgID, 10), "content": string(e.Message.Message), "created_at": e.Message.CreatedAt, - "chat": hex.EncodeToString(e.Chat.Partner), + "chat": e.Chat.ID, "received": e.Message.Received, "dapp": dapp, + "sender": e.Message.Sender, }) } @@ -153,9 +152,10 @@ func (c *Chat) handlePersistedMessage(e db.MessagePersistedEvent) { "db_id": strconv.FormatInt(e.Message.UniqueMsgID, 10), "content": string(e.Message.Message), "created_at": e.Message.CreatedAt, - "chat": hex.EncodeToString(e.Chat.Partner), + "chat": e.Chat.ID, "received": e.Message.Received, "dapp": dapp, + "sender": e.Message.Sender, }) } @@ -164,9 +164,10 @@ func (c *Chat) handlePersistedMessage(e db.MessagePersistedEvent) { "db_id": strconv.FormatInt(e.Message.UniqueMsgID, 10), "content": string(e.Message.Message), "created_at": e.Message.CreatedAt, - "chat": hex.EncodeToString(e.Chat.Partner), + "chat": e.Chat.ID, "received": e.Message.Received, "dapp": dapp, + "sender": e.Message.Sender, }) } diff --git a/chat/queue_processor.go b/chat/queue_processor.go index 715f130..574977f 100644 --- a/chat/queue_processor.go +++ b/chat/queue_processor.go @@ -1,14 +1,11 @@ package chat import ( - "encoding/base64" - "encoding/json" + "encoding/hex" "errors" - "fmt" db "github.com/Bit-Nation/panthalassa/db" queue "github.com/Bit-Nation/panthalassa/queue" - ed25519 "golang.org/x/crypto/ed25519" ) // processor that submits messages from the queue to the backend @@ -39,50 +36,21 @@ func (p *SubmitMessagesProcessor) ValidJob(j queue.Job) error { } // get data from job -func (p *SubmitMessagesProcessor) jobToData(j queue.Job) (ed25519.PublicKey, int64, error) { - // validate message id - var messageId int64 - var messageIdErr error - messageIdInterface, oki := j.Data["db_message_id"] +func (p *SubmitMessagesProcessor) jobToData(j queue.Job) (int, int64, error) { + + // fetch chatID + chatID, oki := j.Data["chat_id"].(int) if !oki { - return nil, 0, errors.New("db_message_id is missing") - } - switch msgId := messageIdInterface.(type) { - case json.Number: - messageId, messageIdErr = msgId.Int64() - if messageIdErr != nil { - return nil, 0, messageIdErr - } - case int64: - messageId = msgId - default: - return nil, 0, errors.New("Expected : json.Number/int64, Got : " + fmt.Sprint(msgId)) + return 0, 0, errors.New("expected chat id to be a float64") } - if messageId == 0 { - return nil, 0, errors.New("message id is 0") - } - // check partner - var partner []byte - var partnerErr error - partnerInterface, oki := j.Data["partner"] + + messageID, oki := j.Data["db_message_id"].(int64) if !oki { - return nil, 0, errors.New("partner is missing") + return 0, 0, errors.New("expected message id to be a float64") } - switch potentialPartner := partnerInterface.(type) { - case string: - partner, partnerErr = base64.StdEncoding.DecodeString(potentialPartner) - if partnerErr != nil { - return nil, 0, partnerErr - } - case ed25519.PublicKey: - partner = potentialPartner - default: - return nil, 0, errors.New("Expected : base64 string/ed25519.PublicKey, Got : " + fmt.Sprint(potentialPartner)) - } - if len(partner) != 32 { - return nil, 0, errors.New("invalid partner id length") - } - return partner, messageId, nil + + return int(chatID), messageID, nil + } func (p *SubmitMessagesProcessor) Process(j queue.Job) error { @@ -93,12 +61,12 @@ func (p *SubmitMessagesProcessor) Process(j queue.Job) error { } // get data from job map - partner, messageID, err := p.jobToData(j) + chatId, messageID, err := p.jobToData(j) if err != nil { return err } - chat, err := p.chatDB.GetChat(partner) + chat, err := p.chatDB.GetChat(chatId) if err != nil { return err } @@ -112,10 +80,30 @@ func (p *SubmitMessagesProcessor) Process(j queue.Job) error { return errors.New("failed to fetch message") } - // send message - err = p.chat.SendMessage(partner, *msg) - if err != nil { - return err + if chat.IsGroupChat() { + + for _, groupMember := range chat.Partners { + + // make sure we don't send to our self + idPubKey, err := p.chat.km.IdentityPublicKey() + if err != nil { + return err + } + if idPubKey == hex.EncodeToString(groupMember) { + continue + } + + msg.GroupChatID = chat.GroupChatRemoteID + if err := p.chat.SendMessage(groupMember, *msg); err != nil { + return err + } + + } + } else { + err = p.chat.SendMessage(chat.Partner, *msg) + if err != nil { + return err + } } // delete job diff --git a/chat/queue_processor_test.go b/chat/queue_processor_test.go index 16cd4a5..0a8cf3f 100644 --- a/chat/queue_processor_test.go +++ b/chat/queue_processor_test.go @@ -1,25 +1,20 @@ package chat import ( - "crypto/rand" "testing" queue "github.com/Bit-Nation/panthalassa/queue" require "github.com/stretchr/testify/require" - ed25519 "golang.org/x/crypto/ed25519" ) func TestSubmitMessagesProcessor_ValidJob(t *testing.T) { - pub, _, err := ed25519.GenerateKey(rand.Reader) - require.Nil(t, err) - p := SubmitMessagesProcessor{} - err = p.ValidJob(queue.Job{ + err := p.ValidJob(queue.Job{ Type: "MESSAGE:SUBMIT", Data: map[string]interface{}{ "db_message_id": int64(3), - "partner": pub, + "chat_id": 3, }, }) require.Nil(t, err) diff --git a/chat/receive_message.go b/chat/receive_message.go index 223ac0f..df75c6b 100644 --- a/chat/receive_message.go +++ b/chat/receive_message.go @@ -117,16 +117,17 @@ func (c *Chat) handleReceivedMessage(msg *bpb.ChatMessage) error { logger.Debugf("double ratchet message %s", drMessage) // make sure chat exist - chat, err := c.chatStorage.GetChat(msg.Sender) + chat, err := c.chatStorage.GetChatByPartner(msg.Sender) if err != nil { return err } if chat == nil { - if err := c.chatStorage.CreateChat(msg.Sender); err != nil { + _, err = c.chatStorage.CreateChat(msg.Sender) + if err != nil { return err } } - chat, err = c.chatStorage.GetChat(msg.Sender) + chat, err = c.chatStorage.GetChatByPartner(msg.Sender) if err != nil { return err } @@ -189,14 +190,28 @@ func (c *Chat) handleReceivedMessage(msg *bpb.ChatMessage) error { if err != nil { return err } + // convert proto plain message to decrypted message dbMessage, err := protoPlainMsgToMessage(&decryptedMsg) + if err != nil { + return err + } dbMessage.Status = db.StatusPersisted dbMessage.Sender = sender dbMessage.Received = true - if err != nil { - return err + + // persist group chat message in the correct chat + if len(dbMessage.GroupChatID) == 200 && dbMessage.AddUserToChat == nil { + groupChat, err := c.chatStorage.GetGroupChatByRemoteID(dbMessage.GroupChatID) + if err != nil { + return err + } + if groupChat == nil { + return fmt.Errorf("couldn't find group chat for id: %x", dbMessage.GroupChatID) + } + return groupChat.PersistMessage(dbMessage) } + return chat.PersistMessage(dbMessage) } @@ -284,11 +299,41 @@ func (c *Chat) handleReceivedMessage(msg *bpb.ChatMessage) error { // convert plain protobuf message to database message dbMessage, err := protoPlainMsgToMessage(&plainMsg) + if err != nil { + return err + } dbMessage.Sender = sender dbMessage.Status = db.StatusPersisted dbMessage.Received = true - if err != nil { - return err + + if dbMessage.AddUserToChat != nil { + + // fetch group chat + groupChat, err := c.chatStorage.GetGroupChatByRemoteID(dbMessage.AddUserToChat.ChatID) + if err != nil { + return err + } + + // when the group chat doesn't exist we need to create it + if groupChat == nil { + return c.chatStorage.CreateGroupChatFromMsg(dbMessage) + } + + return nil + + } + + // persist group chat message in the correct chat + if len(dbMessage.GroupChatID) == 200 && dbMessage.AddUserToChat == nil { + + groupChat, err := c.chatStorage.GetGroupChatByRemoteID(dbMessage.GroupChatID) + if err != nil { + return err + } + if groupChat == nil { + return fmt.Errorf("couldn't find group chat for id: %x", dbMessage.GroupChatID) + } + return groupChat.PersistMessage(dbMessage) } return chat.PersistMessage(dbMessage) @@ -329,18 +374,46 @@ func (c *Chat) handleReceivedMessage(msg *bpb.ChatMessage) error { return err } - // persist message - if err := chat.PersistMessage(dbMessage); err != nil { - return err - } - // if the decryption didn't fail we want to mark // the shared secret as accepted if !sharedSec.Accepted { - fmt.Println("going to accept shared secret") - return c.sharedSecStorage.Accept(sharedSec) + if err := c.sharedSecStorage.Accept(sharedSec); err != nil { + return err + } + } + + // create group chat / add members + if dbMessage.AddUserToChat != nil { + + // fetch group chat + groupChat, err := c.chatStorage.GetGroupChatByRemoteID(dbMessage.AddUserToChat.ChatID) + if err != nil { + return err + } + + // @todo add handle of new user + + // when the group chat doesn't exist we need to create it + if groupChat == nil { + return c.chatStorage.CreateGroupChatFromMsg(dbMessage) + } + + return nil + + } + + // persist group chat message in the correct chat + if len(dbMessage.GroupChatID) == 200 && dbMessage.AddUserToChat == nil { + groupChat, err := c.chatStorage.GetGroupChatByRemoteID(dbMessage.GroupChatID) + if err != nil { + return err + } + if groupChat == nil { + return fmt.Errorf("couldn't find group chat for id: %x", dbMessage.GroupChatID) + } + return groupChat.PersistMessage(dbMessage) } - return nil + return chat.PersistMessage(dbMessage) } diff --git a/chat/save_message.go b/chat/save_message.go index ee95864..adb7f70 100644 --- a/chat/save_message.go +++ b/chat/save_message.go @@ -2,20 +2,18 @@ package chat import ( "encoding/hex" - "errors" + "fmt" "time" db "github.com/Bit-Nation/panthalassa/db" uuid "github.com/satori/go.uuid" - ed25519 "golang.org/x/crypto/ed25519" ) var nowAsUnix = func() int64 { return time.Now().UnixNano() } -// persist private message -func (c *Chat) SavePrivateMessage(to ed25519.PublicKey, rawMessage []byte) error { +func (c *Chat) SaveMessage(chatID int, rawMessage []byte) error { id, err := uuid.NewV4() if err != nil { return err @@ -38,24 +36,14 @@ func (c *Chat) SavePrivateMessage(to ed25519.PublicKey, rawMessage []byte) error Status: db.StatusPersisted, Sender: sender, } + // fetch chat - chat, err := c.chatStorage.GetChat(to) - if err != nil { - return err - } - if chat == nil { - // create chat if not exist - if err := c.chatStorage.CreateChat(to); err != nil { - return err - } - } - // fetch chat again - chat, err = c.chatStorage.GetChat(to) + chat, err := c.chatStorage.GetChat(chatID) if err != nil { return err } if chat == nil { - return errors.New("got invalid chat") + return fmt.Errorf("got invalid chat for id: %d", chatID) } return chat.PersistMessage(msg) diff --git a/chat/save_message_test.go b/chat/save_message_test.go index 67fc35b..140897b 100644 --- a/chat/save_message_test.go +++ b/chat/save_message_test.go @@ -16,11 +16,19 @@ func TestChat_SavePrivateMessage(t *testing.T) { km := createKeyManager() + chatStorage := db.NewChatStorage(createStorm(), []func(e db.MessagePersistedEvent){}, km) + c := Chat{ - chatStorage: db.NewChatStorage(createStorm(), []func(e db.MessagePersistedEvent){}, km), + chatStorage: chatStorage, km: km, } - require.Nil(t, c.SavePrivateMessage(pub, []byte("hi"))) + _, err = chatStorage.CreateChat(pub) + require.Nil(t, err) + + chat, err := chatStorage.GetChatByPartner(pub) + require.Nil(t, err) + + require.Nil(t, c.SaveMessage(chat.ID, []byte("hi"))) } diff --git a/chat/send_and_receive_func_test.go b/chat/send_and_receive_func_test.go index 6627297..4ce5e53 100644 --- a/chat/send_and_receive_func_test.go +++ b/chat/send_and_receive_func_test.go @@ -2,7 +2,6 @@ package chat import ( "encoding/hex" - "fmt" "testing" "time" @@ -13,6 +12,7 @@ import ( uiapi "github.com/Bit-Nation/panthalassa/uiapi" bpb "github.com/Bit-Nation/protobuffers" require "github.com/stretchr/testify/require" + ed25519 "golang.org/x/crypto/ed25519" ) type chatTestBackendTransport struct { @@ -180,8 +180,6 @@ func TestChatBetweenAliceAndBob(t *testing.T) { continue } - fmt.Println(msg) - } // alice case msg := <-aliceTrans.sendChan: @@ -241,7 +239,13 @@ func TestChatBetweenAliceAndBob(t *testing.T) { require.Nil(t, err) // persist private message for bob - require.Nil(t, alice.SavePrivateMessage(bobIDKey, []byte("hi bob"))) + _, err = alice.chatStorage.CreateChat(bobIDKey) + require.Nil(t, err) + bobChat, err := alice.chatStorage.GetChatByPartner(bobIDKey) + require.Nil(t, err) + require.NotNil(t, bobChat) + + require.Nil(t, alice.SaveMessage(bobChat.ID, []byte("hi bob"))) // done signal done := make(chan struct{}, 1) @@ -267,7 +271,6 @@ func TestChatBetweenAliceAndBob(t *testing.T) { // make sure shared secret got accepted shSec, err := alice.sharedSecStorage.GetYoungest(bobIDKey) - fmt.Println(shSec.ID) require.Nil(t, err) require.NotNil(t, shSec) require.True(t, shSec.Accepted) @@ -295,12 +298,230 @@ func TestChatBetweenAliceAndBob(t *testing.T) { require.Equal(t, hex.EncodeToString(aliceIDKey), hex.EncodeToString(shSec.Partner)) require.True(t, shSec.Accepted) - err = bob.SavePrivateMessage(aliceIDKey, []byte("hi alice")) + // fetch chat + chat, err := bob.chatStorage.GetChatByPartner(aliceIDKey) + require.Nil(t, err) + + err = bob.SaveMessage(chat.ID, []byte("hi alice")) + require.Nil(t, err) + } + + case <-done: + return + } + } + +} + +func TestGroupChatBetweenAliceAndBob(t *testing.T) { + // log.SetDebugLogging() + alice, aliceTrans, bob, bobTrans, err := createAliceAndBob() + + // listen for bob's messages + bobReceivedMsgChan := make(chan db.MessagePersistedEvent, 10) + bob.chatStorage.AddListener(func(e db.MessagePersistedEvent) { + bobReceivedMsgChan <- e + }) + bobIDKeyStr, err := bob.km.IdentityPublicKey() + require.Nil(t, err) + bobIDKey, err := hex.DecodeString(bobIDKeyStr) + require.Nil(t, err) + + // listen for alice messages + aliceReceivedMsgChan := make(chan db.MessagePersistedEvent, 10) + alice.chatStorage.AddListener(func(e db.MessagePersistedEvent) { + aliceReceivedMsgChan <- e + }) + aliceIDKeyStr, err := alice.km.IdentityPublicKey() + require.Nil(t, err) + aliceIDKey, err := hex.DecodeString(aliceIDKeyStr) + require.Nil(t, err) + + // bob go + go func() { + + bobSignedPreKey := new(bpb.PreKey) + bobSignedPreKey = nil + + aliceSignedPreKey := new(bpb.PreKey) + aliceSignedPreKey = nil + + for { + select { + case msg := <-bobTrans.sendChan: + + if msg.Request != nil { + + // handle uploaded pre key + if msg.Request.NewSignedPreKey != nil { + bobSignedPreKey = msg.Request.NewSignedPreKey + bobTrans.receiveChan <- &bpb.BackendMessage{ + RequestID: msg.RequestID, + } + continue + } + + if len(msg.Request.PreKeyBundle) != 0 { + + aliceProfile, err := profile.SignProfile("bob", "", "", *bob.km) + if err != nil { + panic(err) + } + aliceProfileProto, err := aliceProfile.ToProtobuf() + if err != nil { + panic(err) + } + + bobTrans.receiveChan <- &bpb.BackendMessage{ + RequestID: msg.RequestID, + Response: &bpb.BackendMessage_Response{ + PreKeyBundle: &bpb.BackendMessage_PreKeyBundle{ + SignedPreKey: aliceSignedPreKey, + Profile: aliceProfileProto, + }, + }, + } + continue + } + + // handle message send to bob + // (the nice thing is that we can write it from alice to bob :)) + if len(msg.Request.Messages) != 0 { + aliceTrans.receiveChan <- msg + bobTrans.receiveChan <- &bpb.BackendMessage{ + RequestID: msg.RequestID, + } + continue + } + + } + // alice + case msg := <-aliceTrans.sendChan: + + if msg.Request != nil { + // handle the pre key bundle request for bob + if len(msg.Request.PreKeyBundle) != 0 { + + bobProfile, err := profile.SignProfile("bob", "", "", *bob.km) + if err != nil { + panic(err) + } + bobProfileProto, err := bobProfile.ToProtobuf() + if err != nil { + panic(err) + } + + <-time.After(time.Second) + + aliceTrans.receiveChan <- &bpb.BackendMessage{ + RequestID: msg.RequestID, + Response: &bpb.BackendMessage_Response{ + PreKeyBundle: &bpb.BackendMessage_PreKeyBundle{ + SignedPreKey: bobSignedPreKey, + Profile: bobProfileProto, + }, + }, + } + continue + } + + // handle upload of our new pre key bundle + if msg.Request.NewSignedPreKey != nil { + aliceSignedPreKey = msg.Request.NewSignedPreKey + aliceTrans.receiveChan <- &bpb.BackendMessage{ + RequestID: msg.RequestID, + } + continue + } + + // handle message send to bob + // (the nice thing is that we can write it from alice to bob :)) + if len(msg.Request.Messages) != 0 { + bobTrans.receiveChan <- msg + aliceTrans.receiveChan <- &bpb.BackendMessage{ + RequestID: msg.RequestID, + } + continue + } + } + + } + + } + }() + + require.Nil(t, err) + + groupChatID, err := alice.CreateGroupChat([]ed25519.PublicKey{bobIDKey}, "Group between alice and bob") + require.Nil(t, err) + require.Nil(t, alice.SaveMessage(groupChatID, []byte("hi @all"))) + + // done signal + done := make(chan struct{}, 1) + + for { + + select { + case msgEv := <-aliceReceivedMsgChan: + + msg := msgEv.Message + + if msg.Received { + + // make sure message is as we expect it to be + require.Equal(t, "Greeting @all", string(msg.Message)) + require.Equal(t, "Group between alice and bob", msgEv.Chat.Name) + require.Equal(t, hex.EncodeToString(bobIDKey), hex.EncodeToString(msg.Sender)) + require.Equal(t, uint(1), msg.Version) + require.Equal(t, db.StatusPersisted, msg.Status) + + // we need to wait a bit since this is called async + // before the receive handling logic has the chance to update + // the shared secret + time.Sleep(time.Second) + + // make sure shared secret got accepted + shSec, err := alice.sharedSecStorage.GetYoungest(bobIDKey) + require.Nil(t, err) + require.NotNil(t, shSec) + require.True(t, shSec.Accepted) + + done <- struct{}{} + } + + case msgEv := <-bobReceivedMsgChan: + + msg := msgEv.Message + + // handle received messages + if msg.Received { + + // make sure group ID has been set + require.Equal(t, 200, len(msgEv.Chat.GroupChatRemoteID)) + + // make sure the messages is as we expect it to be + require.Equal(t, "hi @all", string(msg.Message)) + require.Equal(t, "Group between alice and bob", msgEv.Chat.Name) + require.True(t, msg.Received) + require.Equal(t, db.StatusPersisted, msg.Status) + require.Equal(t, uint(1), msg.Version) + + // make sure shared secret got persisted + shSec, err := bob.sharedSecStorage.GetYoungest(aliceIDKey) require.Nil(t, err) + require.NotNil(t, shSec) + require.Equal(t, hex.EncodeToString(aliceIDKey), hex.EncodeToString(shSec.Partner)) + require.True(t, shSec.Accepted) + + // send message back + require.Nil(t, msgEv.Chat.SaveMessage([]byte("Greeting @all"))) + } case <-done: return + case <-time.After(time.Second * 6): + require.FailNow(t, "timed out") } } diff --git a/chat/send_message.go b/chat/send_message.go index 8fbc98c..9029295 100644 --- a/chat/send_message.go +++ b/chat/send_message.go @@ -30,7 +30,28 @@ func (c *Chat) SendMessage(receiver ed25519.PublicKey, dbMessage db.Message) err Message: dbMessage.Message, MessageID: dbMessage.ID, // this version is NOT the same as the version from the database message - Version: 1, + Version: 1, + GroupChatID: dbMessage.GroupChatID, + } + + // attach add user data + if dbMessage.AddUserToChat != nil { + addUserMsg := dbMessage.AddUserToChat + groupChat, err := c.chatStorage.GetGroupChatByRemoteID(dbMessage.GroupChatID) + if err != nil { + return err + } + plainMessage.AddUserPrivChat = &bpb.PlainChatMessage_AddUserPrivGroupChat{ + Users: func() [][]byte { + users := [][]byte{} + for _, u := range dbMessage.AddUserToChat.Users { + users = append(users, u) + } + return users + }(), + ChatID: addUserMsg.ChatID, + GroupName: groupChat.GroupChatName, + } } // in the case this is a DApp message, diff --git a/chat/utils.go b/chat/utils.go index 133aa53..8804608 100644 --- a/chat/utils.go +++ b/chat/utils.go @@ -14,8 +14,8 @@ import ( ed25519 "golang.org/x/crypto/ed25519" ) -func (c *Chat) MarkMessagesAsRead(partner ed25519.PublicKey) error { - return c.chatStorage.ReadMessages(partner) +func (c *Chat) MarkMessagesAsRead(chatID int) error { + return c.chatStorage.ReadMessages(chatID) } type drDhPair struct { @@ -165,12 +165,21 @@ func hashChatMessage(msg bpb.ChatMessage) (mh.Multihash, error) { // turns a received plain protobuf message into a database message func protoPlainMsgToMessage(msg *bpb.PlainChatMessage) (db.Message, error) { - m := db.Message{ - ID: msg.MessageID, - Message: msg.Message, - CreatedAt: msg.CreatedAt, - Version: uint(msg.Version), + ID: msg.MessageID, + Message: msg.Message, + CreatedAt: msg.CreatedAt, + Version: uint(msg.Version), + GroupChatID: msg.GroupChatID, + } + + if msg.AddUserPrivChat != nil { + m.AddUserToChat = &db.AddUserToChat{} + m.AddUserToChat.ChatID = msg.AddUserPrivChat.ChatID + for _, user := range msg.AddUserPrivChat.Users { + m.AddUserToChat.Users = append(m.AddUserToChat.Users, user) + } + m.AddUserToChat.ChatName = msg.AddUserPrivChat.GroupName } if isDAppMessage(msg) { diff --git a/dapp/dapp.go b/dapp/dapp.go index 637fdea..d7dd68d 100644 --- a/dapp/dapp.go +++ b/dapp/dapp.go @@ -14,13 +14,13 @@ import ( storm "github.com/asdine/storm" log "github.com/ipfs/go-log" logger "github.com/op/go-logging" - duktape "gopkg.in/olebedev/go-duktape.v3" + otto "github.com/robertkrimen/otto" ) var sysLog = log.Logger("dapp") type DApp struct { - vm *duktape.Context + vm *otto.Otto logger *logger.Logger app *Data // will be called when the app shut down @@ -34,14 +34,15 @@ type DApp struct { // close DApp func (d *DApp) Close() { - defer d.vm.DestroyHeap() - d.logger.Info(fmt.Sprintf("shutting down: %s (%s)", hex.EncodeToString(d.app.UsedSigningKey), d.app.Name)) - for _, mod := range d.vmModules { - if err := mod.Close(); err != nil { - sysLog.Error(err) + d.vm.Interrupt <- func() { + d.logger.Info(fmt.Sprintf("shutting down: %s (%s)", hex.EncodeToString(d.app.UsedSigningKey), d.app.Name)) + for _, mod := range d.vmModules { + if err := mod.Close(); err != nil { + sysLog.Error(err) + } } + d.closeChan <- d.app } - d.closeChan <- d.app } func (d *DApp) ID() string { @@ -73,7 +74,8 @@ func New(l *logger.Logger, app *Data, vmModules []module.Module, closer chan<- * } // create VM - vm := duktape.New() + vm := otto.New() + vm.Interrupt = make(chan func(), 1) // register all vm modules for _, m := range vmModules { @@ -130,8 +132,7 @@ func New(l *logger.Logger, app *Data, vmModules []module.Module, closer chan<- * // start the DApp async go func() { - // Synonymous to vm.Run in Otto - err := vm.PevalString(string(app.Code)) + _, err := vm.Run(app.Code) if err != nil { l.Errorf(err.Error()) } @@ -146,6 +147,7 @@ func New(l *logger.Logger, app *Data, vmModules []module.Module, closer chan<- * } return dApp, nil case <-time.After(timeOut): + vm.Interrupt <- func() {} closer <- app return nil, errors.New("timeout - failed to start DApp") } diff --git a/dapp/dapp_storage_test.go b/dapp/dapp_storage_test.go index f750d43..f8bb40a 100644 --- a/dapp/dapp_storage_test.go +++ b/dapp/dapp_storage_test.go @@ -11,8 +11,8 @@ import ( uiApi "github.com/Bit-Nation/panthalassa/uiapi" storm "github.com/asdine/storm" - bolt "github.com/coreos/bbolt" require "github.com/stretchr/testify/require" + bolt "go.etcd.io/bbolt" ed25519 "golang.org/x/crypto/ed25519" ) diff --git a/dapp/module/callbacks/module.go b/dapp/module/callbacks/module.go index f102ca3..ab50a36 100644 --- a/dapp/module/callbacks/module.go +++ b/dapp/module/callbacks/module.go @@ -8,7 +8,7 @@ import ( validator "github.com/Bit-Nation/panthalassa/dapp/validator" log "github.com/ipfs/go-log" logger "github.com/op/go-logging" - duktape "gopkg.in/olebedev/go-duktape.v3" + otto "github.com/robertkrimen/otto" ) var debugger = log.Logger("callbacks") @@ -34,7 +34,7 @@ func New(l *logger.Logger) *Module { var count uint - functions := map[uint]*duktape.Context{} + functions := map[uint]*otto.Value{} cbChans := map[*chan error]bool{} for { @@ -110,7 +110,7 @@ func New(l *logger.Logger) *Module { } type addFunction struct { - fn *duktape.Context + fn *otto.Value respChan chan struct { id uint error error @@ -119,12 +119,12 @@ type addFunction struct { type fetchFunction struct { id uint - respChan chan *duktape.Context + respChan chan *otto.Value } type Module struct { logger *logger.Logger - vm *duktape.Context + vm *otto.Otto reqLim *reqLim.Count addFunctionChan chan addFunction fetchFunctionChan chan fetchFunction @@ -146,33 +146,37 @@ func (m *Module) Close() error { // e.g. myRegisteredFunction(payloadObj, cb) // the callback must be called in order to "return" from the function func (m *Module) CallFunction(id uint, payload string) error { + debugger.Debug(fmt.Errorf("call function with id: %d and payload: %s", id, payload)) - respChan := make(chan *duktape.Context) + respChan := make(chan *otto.Value) m.fetchFunctionChan <- fetchFunction{ id: id, respChan: respChan, } - vm := <-respChan + fn := <-respChan - if vm == nil || vm.GetType(0).IsNone() { + if fn == nil { return errors.New(fmt.Sprintf("function with id: %d does not exist", id)) } // check if function is registered - if !vm.IsFunction(0) { + if !fn.IsFunction() { return errors.New(fmt.Sprintf("function with id: %d does not exist", id)) } // parse params - objArgs := "(" + payload + ")" + objArgs, err := m.vm.Object("(" + payload + ")") + if err != nil { + return err + } done := make(chan error, 1) m.addCBChan <- &done alreadyCalled := false - _, err := vm.PushGlobalGoFunction("callbackCallFunction", func(context *duktape.Context) int { + _, err = fn.Call(*fn, objArgs, func(call otto.FunctionCall) otto.Value { defer func() { m.rmCBChan <- &done @@ -181,43 +185,26 @@ func (m *Module) CallFunction(id uint, payload string) error { // check if callback has already been called if alreadyCalled { m.logger.Error("Already called callback") - if context.IsFunction(1) { - context.PushString("Callback: Already called callback") - context.Call(1) - } - return 1 + return m.vm.MakeCustomError("Callback", "Already called callback") } alreadyCalled = true - if !context.IsUndefined(0) { - firstParameter := context.ToString(0) - if context.IsFunction(1) { - context.PushString(firstParameter) - context.Call(1) - } - done <- errors.New(firstParameter) - return 1 + // check parameters + err := call.Argument(0) + + if !err.IsUndefined() { + done <- errors.New(err.String()) + return otto.Value{} } done <- nil - return 0 + return otto.Value{} }) if err != nil { m.logger.Error(err.Error()) } - // Push objArgs to the stack as first parameter - err = vm.PevalString(objArgs) - if err != nil { - m.logger.Error(err.Error()) - } - // Push callbackCallFunction to the stack as second parameter - err = vm.PevalString(`callbackCallFunction`) - if err != nil { - m.logger.Error(err.Error()) - } - // Use 2 when calling as we just pushed 2 parameters to the stack - vm.Call(2) + return <-done } @@ -228,17 +215,20 @@ func (m *Module) CallFunction(id uint, payload string) error { // a registered function will be called with an object containing information // and a callback that should be called (with an optional error) in order to // "return" -func (m *Module) Register(vm *duktape.Context) error { +func (m *Module) Register(vm *otto.Otto) error { m.vm = vm - _, err := vm.PushGlobalGoFunction("registerFunction", func(context *duktape.Context) int { + err := vm.Set("registerFunction", func(call otto.FunctionCall) otto.Value { // validate function call v := validator.New() v.Set(0, &validator.TypeFunction) - if err := v.Validate(context); err != nil { - m.logger.Error(fmt.Sprintf(`registerFunction needs a callback as it's first param %s`, err.Error())) - return 1 + if err := v.Validate(vm, call); err != nil { + m.logger.Error(fmt.Sprintf(`registerFunction needs a callback as it's first param %s`, err.String())) + return *err } + // callback + fn := call.Argument(0) + // add function to stack idRespChan := make(chan struct { id uint @@ -246,7 +236,7 @@ func (m *Module) Register(vm *duktape.Context) error { }, 1) m.addFunctionChan <- addFunction{ respChan: idRespChan, - fn: context, + fn: &fn, } // response addResp := <-idRespChan @@ -254,11 +244,14 @@ func (m *Module) Register(vm *duktape.Context) error { // exit vm on error if addResp.error != nil { m.logger.Error(addResp.error.Error()) - return 1 + return otto.Value{} } - // convert function id to int - functionId := int(addResp.id) + // convert function id to otto value + functionId, err := otto.ToValue(addResp.id) + if err != nil { + m.logger.Error(err.Error()) + } return functionId @@ -267,22 +260,27 @@ func (m *Module) Register(vm *duktape.Context) error { return err } - _, err = vm.PushGlobalGoFunction("unRegisterFunction", func(context *duktape.Context) int { + return vm.Set("unRegisterFunction", func(call otto.FunctionCall) otto.Value { // validate function call v := validator.New() v.Set(0, &validator.TypeNumber) - if err := v.Validate(context); err != nil { - return 1 + if err := v.Validate(vm, call); err != nil { + return *err } // function id - funcID := context.ToInt(0) + funcID := call.Argument(0) + id, err := funcID.ToInteger() + if err != nil { + m.logger.Error(err.Error()) + return otto.Value{} + } // delete function from channel - m.deleteFunctionChan <- uint(funcID) + m.deleteFunctionChan <- uint(id) - return 0 + return otto.Value{} }) - return err + } diff --git a/dapp/module/callbacks/module_test.go b/dapp/module/callbacks/module_test.go index 708fcd9..c8569a6 100644 --- a/dapp/module/callbacks/module_test.go +++ b/dapp/module/callbacks/module_test.go @@ -1,37 +1,35 @@ package callbacks import ( - "strconv" "testing" "time" log "github.com/op/go-logging" + otto "github.com/robertkrimen/otto" require "github.com/stretchr/testify/require" - duktape "gopkg.in/olebedev/go-duktape.v3" ) func TestFuncRegistration(t *testing.T) { m := New(nil) - vm := duktape.New() + vm := otto.New() require.Nil(t, m.Register(vm)) require.Equal(t, uint(0), m.reqLim.Count()) - funcId, err := vm.PushGlobalGoFunction("callbackTestFuncRegistration", func(context *duktape.Context) int { - return 0 + funcId, err := vm.Call(`registerFunction`, vm, func(call otto.FunctionCall) otto.Value { + return otto.Value{} }) require.Nil(t, err) - err = vm.PevalString(`registerFunction(callbackTestFuncRegistration)`) - require.Nil(t, err) require.Equal(t, uint(1), m.reqLim.Count()) - id := funcId - require.Equal(t, int(1), id) + id, err := funcId.ToInteger() + require.Nil(t, err) + require.Equal(t, int64(1), id) // fetch function and assert - respChan := make(chan *duktape.Context) + respChan := make(chan *otto.Value) m.fetchFunctionChan <- fetchFunction{ id: 1, respChan: respChan, @@ -43,22 +41,22 @@ func TestFuncRegistration(t *testing.T) { func TestFuncUnRegisterSuccess(t *testing.T) { m := New(nil) - vm := duktape.New() + vm := otto.New() require.Nil(t, m.Register(vm)) // register function - funcId, err := vm.PushGlobalGoFunction("callbackTestFuncUnRegisterSuccess", func(context *duktape.Context) int { - return 0 + funcId, err := vm.Call(`registerFunction`, vm, func(call otto.FunctionCall) otto.Value { + return otto.Value{} }) require.Nil(t, err) - err = vm.PevalString(`registerFunction(callbackTestFuncUnRegisterSuccess)`) - require.Nil(t, err) + // make sure the id is the one we expect it to be - id := funcId - require.Equal(t, int(1), id) + id, err := funcId.ToInteger() + require.Nil(t, err) + require.Equal(t, int64(1), id) // make sure function exist - respChan := make(chan *duktape.Context) + respChan := make(chan *otto.Value) m.fetchFunctionChan <- fetchFunction{ id: 1, respChan: respChan, @@ -67,15 +65,21 @@ func TestFuncUnRegisterSuccess(t *testing.T) { require.Equal(t, uint(1), m.reqLim.Count()) // un-register function that has never been registered - err = vm.PevalString(`unRegisterFunction(23565)`) + returnedValue, err := vm.Call(`unRegisterFunction`, vm, 23565) + require.Nil(t, err) + returnedValueStr, err := returnedValue.ToString() require.Nil(t, err) + require.Equal(t, "undefined", returnedValueStr) // request limitation must still be one since function that have not been registered // can't decrease the counter require.Equal(t, uint(1), m.reqLim.Count()) // successfully un-register function - err = vm.PevalString(`unRegisterFunction(` + strconv.Itoa(id) + `)`) + returnedValue, err = vm.Call(`unRegisterFunction`, vm, id) require.Nil(t, err) + returnedValueStr, err = returnedValue.ToString() + require.Nil(t, err) + require.Equal(t, "undefined", returnedValueStr) // wait for the go routine to sync up time.Sleep(time.Millisecond * 100) // should be 0 since the the function id we passed in exists @@ -86,40 +90,37 @@ func TestFuncUnRegisterSuccess(t *testing.T) { func TestFuncCallSuccess(t *testing.T) { m := New(nil) - vm := duktape.New() + vm := otto.New() require.Nil(t, m.Register(vm)) - _, err := vm.PushGlobalGoFunction("callbackTestFuncCallSuccess", func(context *duktape.Context) int { + _, err := vm.Call(`registerFunction`, vm, func(call otto.FunctionCall) otto.Value { - if !context.IsObject(0) { - panic("callbackTestFuncCallSuccess : 0 is not an object") - } - if !context.GetPropString(0, "key") { - panic("callbackTestFuncCallSuccess : key missing") + valueFromObj, err := call.Argument(0).Object().Get("key") + if err != nil { + panic(err) } - valueFromObj := context.ToString(-1) - context.Pop() - if valueFromObj != "value" { + if valueFromObj.String() != "value" { panic("expected value of key to be: value") } - if !context.IsFunction(1) { + cb := call.Argument(1) + + if !cb.IsFunction() { panic("expected second argument to be a callback") } - context.PushUndefined() - context.Call(1) + _, err = cb.Call(cb) + if err != nil { + m.logger.Error(err.Error()) + } - return 0 + return otto.Value{} }) require.Nil(t, err) - err = vm.PevalString(`registerFunction(callbackTestFuncCallSuccess)`) - require.Nil(t, err) - // make sure function exist - respChan := make(chan *duktape.Context) + respChan := make(chan *otto.Value) m.fetchFunctionChan <- fetchFunction{ id: 1, respChan: respChan, @@ -134,40 +135,39 @@ func TestFuncCallError(t *testing.T) { m := New(nil) - vm := duktape.New() + vm := otto.New() require.Nil(t, m.Register(vm)) - _, err := vm.PushGlobalGoFunction("callbackTestFuncCallError", func(context *duktape.Context) int { + _, err := vm.Call(`registerFunction`, vm, func(call otto.FunctionCall) otto.Value { - if !context.IsObject(0) { - panic("callbackTestFuncCallSuccess : 0 is not an object") - } - if !context.GetPropString(0, "key") { - panic("callbackTestFuncCallSuccess : key missing") + valueFromObj, err := call.Argument(0).Object().Get("key") + if err != nil { + panic(err) } - valueFromObj := context.ToString(-1) - if valueFromObj != "value" { + if valueFromObj.String() != "value" { panic("expected value of key to be: value") } - if !context.IsFunction(1) { + cb := call.Argument(1) + + if !cb.IsFunction() { panic("expected second argument to be a callback") } - context.Pop() - context.PushString("I am an error") - context.Call(1) + _, err = cb.Call(cb, "I am an error") - return 0 + if err != nil { + m.logger.Error(err.Error()) + } + + return otto.Value{} }) require.Nil(t, err) - err = vm.PevalString(`registerFunction(callbackTestFuncCallError)`) - require.Nil(t, err) // make sure function exist - respChan := make(chan *duktape.Context) + respChan := make(chan *otto.Value) m.fetchFunctionChan <- fetchFunction{ id: 1, respChan: respChan, @@ -182,81 +182,64 @@ func TestFuncCallBackTwice(t *testing.T) { m := New(log.MustGetLogger("")) - vm := duktape.New() + vm := otto.New() require.Nil(t, m.Register(vm)) - _, err := vm.PushGlobalGoFunction("callbackTestFuncCallBackTwiceTestUndefined", func(context *duktape.Context) int { - if !context.IsUndefined(0) { - panic("expected value to be undefined") - } - return 0 - }) - require.Nil(t, err) - _, err = vm.PushGlobalGoFunction("callbackTestFuncCallBackTwiceTestAlreadyCalled", func(context *duktape.Context) int { - if !context.IsString(0) { - panic("expected value to be string") - } - if context.ToString(0) != "Callback: Already called callback" { - panic("Expected an error that tells me that I alrady called the callback") - } - return 0 - }) - require.Nil(t, err) - - _, err = vm.PushGlobalGoFunction("callbackTestFuncCallBackTwice", func(context *duktape.Context) int { + _, err := vm.Call(`registerFunction`, vm, func(call otto.FunctionCall) otto.Value { - if !context.IsObject(0) { - panic("callbackTestFuncCallSuccess : 0 is not an object") - } - if !context.GetPropString(0, "key") { - panic("callbackTestFuncCallSuccess : key missing") + valueFromObj, err := call.Argument(0).Object().Get("key") + if err != nil { + panic(err) } - valueFromObj := context.ToString(-1) - - if valueFromObj != "value" { + if valueFromObj.String() != "value" { panic("expected value of key to be: value") } - if !context.IsFunction(1) { + cb := call.Argument(1) + + if !cb.IsFunction() { panic("expected second argument to be a callback") } - context.Pop() - context.DupTop() - context.PushUndefined() - context.PevalString(`callbackTestFuncCallBackTwiceTestUndefined`) - context.Call(2) - context.Pop() - context.PushUndefined() - context.PevalString(`callbackTestFuncCallBackTwiceTestAlreadyCalled`) - context.Call(2) + val, err := cb.Call(cb) + if err != nil { + panic(err) + } + if !val.IsUndefined() { + panic("expected value to be undefined") + } - return 0 + val, err = cb.Call(cb) + if err != nil { + panic(err) + } + if val.String() != "Callback: Already called callback" { + panic("Expected an error that tells me that I alrady called the callback") + } + + return otto.Value{} }) require.Nil(t, err) - err = vm.PevalString(`registerFunction(callbackTestFuncCallBackTwice)`) - require.Nil(t, err) // make sure function exist - respChan := make(chan *duktape.Context) + respChan := make(chan *otto.Value) m.fetchFunctionChan <- fetchFunction{ id: 1, respChan: respChan, } require.NotNil(t, <-respChan) - err = m.CallFunction(1, `{key: "value"}`) - require.Nil(t, err) + m.CallFunction(1, `{key: "value"}`) } func TestModule_CallFunctionThatIsNotRegistered(t *testing.T) { m := New(log.MustGetLogger("")) - vm := duktape.New() + vm := otto.New() require.Nil(t, m.Register(vm)) err := m.CallFunction(1, "{}") @@ -267,19 +250,16 @@ func TestModule_CallFunctionThatIsNotRegistered(t *testing.T) { func TestModule_Close(t *testing.T) { m := New(log.MustGetLogger("")) - vm := duktape.New() + vm := otto.New() require.Nil(t, m.Register(vm)) - _, err := vm.PushGlobalGoFunction("callbackTestModuleClose", func(context *duktape.Context) int { + // register function + _, err := vm.Call("registerFunction", vm, func(call otto.FunctionCall) otto.Value { m.Close() - return 0 + return otto.Value{} }) require.Nil(t, err) - // register function - err = vm.PevalString(`registerFunction(callbackTestModuleClose)`) - require.Nil(t, err) - require.EqualError(t, m.CallFunction(1, "{}"), "closed application") } diff --git a/dapp/module/db/db.go b/dapp/module/db/db.go index 533b3e6..c48c6f4 100644 --- a/dapp/module/db/db.go +++ b/dapp/module/db/db.go @@ -3,14 +3,13 @@ package db import ( "encoding/json" "errors" - "fmt" "time" reqLim "github.com/Bit-Nation/panthalassa/dapp/request_limitation" validator "github.com/Bit-Nation/panthalassa/dapp/validator" log "github.com/ipfs/go-log" opLogger "github.com/op/go-logging" - duktape "gopkg.in/olebedev/go-duktape.v3" + otto "github.com/robertkrimen/otto" ) var logger = log.Logger("db module") @@ -33,149 +32,155 @@ func (m *Module) Close() error { return nil } -func (m *Module) Register(vm *duktape.Context) error { +func (m *Module) Register(vm *otto.Otto) error { - var itemsToPopBeforeCallback int - handleError := func(errMsg string, context *duktape.Context, position int) int { - if context.IsFunction(position) { - context.PopN(itemsToPopBeforeCallback) - context.PushString(errMsg) - context.Call(1) - return 0 + handleError := func(errMsg string, cb otto.Value) otto.Value { + if cb.IsFunction() { + _, err := cb.Call(cb, errMsg) + if err != nil { + m.logger.Error(errMsg) + } + return otto.Value{} } logger.Error(errMsg) - return 1 + return otto.Value{} } - _, err := vm.PushGlobalGoFunction("dbPut", func(context *duktape.Context) int { - logger.Debug("put value") + return vm.Set("db", map[string]interface{}{ + "put": func(call otto.FunctionCall) otto.Value { - // validate call - v := validator.New() - v.Set(0, &validator.TypeString) - v.Set(2, &validator.TypeFunction) - if err := v.Validate(context); err != nil { - return handleError(err.Error(), context, 2) - } + logger.Debug("put value") - // fetch key and value - key := context.ToString(0) - value := context.ToString(1) - // marshal value into json - byteValue, err := json.Marshal(value) - if err != nil { - return handleError(err.Error(), context, 2) - } + // validate call + v := validator.New() + v.Set(0, &validator.TypeString) + v.Set(2, &validator.TypeFunction) + cb := call.Argument(2) + if err := v.Validate(vm, call); err != nil { + return handleError(err.String(), cb) + } - throttlingFunc := func(dec chan struct{}, vmDone chan struct{}) { - defer func() { - <-vmDone - }() - // persist key and value - if err := m.dAppDB.Put([]byte(key), byteValue); err != nil { - dec <- struct{}{} - handleError(err.Error(), context, 2) + // fetch key and value + key := call.Argument(0) + value, err := call.Argument(1).Export() + if err != nil { + return handleError(err.Error(), cb) } - dec <- struct{}{} - // call callback - context.PopN(itemsToPopBeforeCallback) - context.PushUndefined() - context.Call(1) - } - m.reqLim.Exec(throttlingFunc) - return 0 + // marshal value into json + byteValue, err := json.Marshal(value) + if err != nil { + return handleError(err.Error(), cb) + } - }) + m.reqLim.Exec(func(dec chan struct{}) { - _, err = vm.PushGlobalGoFunction("dbHas", func(context *duktape.Context) int { - //@TODO figure out why logger.Errorf instead of logger.Debug - logger.Errorf("check if value exist") + // persist key and value + if err := m.dAppDB.Put([]byte(key.String()), byteValue); err != nil { + dec <- struct{}{} + handleError(err.Error(), cb) + } + dec <- struct{}{} + // call callback + _, err = cb.Call(cb) + if err != nil { + logger.Error(err.Error()) + } - // validate function call - v := validator.New() - v.Set(0, &validator.TypeString) - v.Set(1, &validator.TypeFunction) + }) - if err := v.Validate(context); err != nil { - return handleError(err.Error(), context, 1) - } + return otto.Value{} - // key of database - key := context.ToString(0) + }, + "has": func(call otto.FunctionCall) otto.Value { - // check if database has value - has, err := m.dAppDB.Has([]byte(key)) - if err != nil { - return handleError(err.Error(), context, 1) - } - context.PopN(itemsToPopBeforeCallback) - context.PushUndefined() - context.PushBoolean(has) - context.Call(2) - return 0 - }) + logger.Errorf("check if value exist") - _, err = vm.PushGlobalGoFunction("dbGet", func(context *duktape.Context) int { - m.logger.Debug("get value") + // validate function call + v := validator.New() + v.Set(0, &validator.TypeString) + v.Set(1, &validator.TypeFunction) + cb := call.Argument(1) + if err := v.Validate(vm, call); err != nil { + return handleError(err.String(), cb) + } - // validate function call - v := validator.New() - v.Set(0, &validator.TypeString) - v.Set(1, &validator.TypeFunction) + // key of database + key := call.Argument(0).String() - if err := v.Validate(context); err != nil { - return handleError(err.Error(), context, 1) - } + // check if database has value + has, err := m.dAppDB.Has([]byte(key)) + if err != nil { + return handleError(err.Error(), cb) + } + _, err = cb.Call(cb, nil, has) + if err != nil { + m.logger.Error(err.Error()) + } + return otto.Value{} - // key of database - key := context.ToString(0) + }, + "get": func(call otto.FunctionCall) otto.Value { - // raw value of key - value, err := m.dAppDB.Get([]byte(key)) - if err != nil { - return handleError(err.Error(), context, 1) - } + m.logger.Debug("get value") - // unmarshal json - var unmarshalledValue interface{} - if err := json.Unmarshal(value, &unmarshalledValue); err != nil { - return handleError(err.Error(), context, 1) - } + // validate function call + v := validator.New() + v.Set(0, &validator.TypeString) + v.Set(1, &validator.TypeFunction) + cb := call.Argument(1) + if err := v.Validate(vm, call); err != nil { + return handleError(err.String(), cb) + } - // call callback with error - context.PopN(itemsToPopBeforeCallback) - context.PushUndefined() - context.PushString(fmt.Sprint(unmarshalledValue)) - context.Call(2) - return 0 - }) + // key of database + key := call.Argument(0).String() - _, err = vm.PushGlobalGoFunction("dbDelete", func(context *duktape.Context) int { + // raw value of key + value, err := m.dAppDB.Get([]byte(key)) + if err != nil { + return handleError(err.Error(), cb) + } - logger.Debug("delete value") + // unmarshal json + var unmarshalledValue interface{} + if err := json.Unmarshal(value, &unmarshalledValue); err != nil { + return handleError(err.Error(), cb) + } - // validate function call - v := validator.New() - v.Set(0, &validator.TypeString) - v.Set(1, &validator.TypeFunction) + // call callback with error + _, err = cb.Call(cb, nil, unmarshalledValue) + if err != nil { + m.logger.Error(err.Error()) + } + return otto.Value{} - if err := v.Validate(context); err != nil { - return handleError(err.Error(), context, 1) - } + }, + "delete": func(call otto.FunctionCall) otto.Value { - // delete value - key := context.ToString(0) - if err := m.dAppDB.Delete([]byte(key)); err != nil { - return handleError(err.Error(), context, 1) - } + logger.Debug("delete value") - context.PopN(itemsToPopBeforeCallback) - context.PushUndefined() - context.Call(1) - return 0 + // validate function call + v := validator.New() + v.Set(0, &validator.TypeString) + v.Set(1, &validator.TypeFunction) + cb := call.Argument(1) + if err := v.Validate(vm, call); err != nil { + return handleError(err.String(), cb) + } - }) + // delete value + key := call.Argument(0) + if err := m.dAppDB.Delete([]byte(key.String())); err != nil { + return handleError(err.Error(), cb) + } - return err + if _, err := cb.Call(cb); err != nil { + logger.Error(err.Error()) + } + + return otto.Value{} + + }, + }) } diff --git a/dapp/module/db/db_test.go b/dapp/module/db/db_test.go index b2d9306..0b50ea2 100644 --- a/dapp/module/db/db_test.go +++ b/dapp/module/db/db_test.go @@ -6,8 +6,8 @@ import ( "time" log "github.com/op/go-logging" + otto "github.com/robertkrimen/otto" require "github.com/stretchr/testify/require" - duktape "gopkg.in/olebedev/go-duktape.v3" ) type inMemoryDB struct { @@ -40,22 +40,19 @@ func TestModulePut(t *testing.T) { storage: map[string][]byte{}, }, log.MustGetLogger("")) - vm := duktape.New() + vm := otto.New() require.Nil(t, m.Register(vm)) closer := make(chan struct{}, 1) - _, err := vm.PushGlobalGoFunction("callbackDbPut", func(context *duktape.Context) int { - errBool := !context.IsUndefined(0) - require.False(t, errBool) + vm.Call("db.put", vm, "key", "value", func(call otto.FunctionCall) otto.Value { + err := call.Argument(0) + require.False(t, err.IsDefined()) closer <- struct{}{} - return 0 + return otto.Value{} }) - require.Nil(t, err) - - vm.PevalString(`dbPut("key","value",callbackDbPut)`) select { case <-closer: @@ -75,26 +72,24 @@ func TestModuleHas(t *testing.T) { }, } - vm := duktape.New() + vm := otto.New() require.Nil(t, m.Register(vm)) closer := make(chan struct{}, 1) + vm.Call("db.has", vm, "key", func(call otto.FunctionCall) otto.Value { - _, err := vm.PushGlobalGoFunction("callbackDbHas", func(context *duktape.Context) int { - errBool := !context.IsUndefined(0) - require.False(t, errBool) + err := call.Argument(0) + require.False(t, err.IsDefined()) - exist := context.IsBoolean(1) + exist, e := call.Argument(1).ToBoolean() + require.Nil(t, e) require.True(t, exist) closer <- struct{}{} - return 0 + return otto.Value{} }) - require.Nil(t, err) - - vm.PevalString(`dbHas("key",callbackDbHas)`) select { case <-closer: @@ -118,26 +113,24 @@ func TestModuleGet(t *testing.T) { logger: log.MustGetLogger(""), } - vm := duktape.New() + vm := otto.New() require.Nil(t, m.Register(vm)) closer := make(chan struct{}, 1) - _, err = vm.PushGlobalGoFunction("callbackDbGet", func(context *duktape.Context) int { + vm.Call("db.get", vm, "key", func(call otto.FunctionCall) otto.Value { - errBool := !context.IsUndefined(0) - require.False(t, errBool) + err := call.Argument(0) + require.False(t, err.IsDefined()) - value := context.ToString(1) + value := call.Argument(1).String() require.Equal(t, "value", value) closer <- struct{}{} - return 0 + return otto.Value{} }) - require.Nil(t, err) - vm.PevalString(`dbGet("key",callbackDbGet)`) select { case <-closer: case <-time.After(time.Second * 2): @@ -159,24 +152,21 @@ func TestModuleDelete(t *testing.T) { }, } - vm := duktape.New() + vm := otto.New() require.Nil(t, m.Register(vm)) closer := make(chan struct{}, 1) + vm.Call("db.delete", vm, "key", func(call otto.FunctionCall) otto.Value { - _, err = vm.PushGlobalGoFunction("callbackDbDelete", func(context *duktape.Context) int { - - errBool := !context.IsUndefined(0) - require.False(t, errBool) + err := call.Argument(0) + require.False(t, err.IsDefined()) closer <- struct{}{} - return 0 + return otto.Value{} }) - vm.PevalString(`dbDelete("key", callbackDbDelete)`) - select { case <-closer: case <-time.After(time.Second * 2): diff --git a/dapp/module/ethAddress/module.go b/dapp/module/ethAddress/module.go index 34daf8e..a96295b 100644 --- a/dapp/module/ethAddress/module.go +++ b/dapp/module/ethAddress/module.go @@ -3,7 +3,7 @@ package ethAddress import ( keyManager "github.com/Bit-Nation/panthalassa/keyManager" log "github.com/ipfs/go-log" - duktape "gopkg.in/olebedev/go-duktape.v3" + otto "github.com/robertkrimen/otto" ) var logger = log.Logger("eth address") @@ -22,7 +22,7 @@ func (m *Module) Close() error { return nil } -func (m *Module) Register(context *duktape.Context) error { +func (m *Module) Register(vm *otto.Otto) error { logger.Debug("get ethereum address") @@ -30,7 +30,7 @@ func (m *Module) Register(context *duktape.Context) error { if err != nil { return err } - context.PushString(addr) - return err + + return vm.Set("ethereumAddress", addr) } diff --git a/dapp/module/ethAddress/module_test.go b/dapp/module/ethAddress/module_test.go index 4e3315b..3af85dd 100644 --- a/dapp/module/ethAddress/module_test.go +++ b/dapp/module/ethAddress/module_test.go @@ -6,8 +6,8 @@ import ( keyManager "github.com/Bit-Nation/panthalassa/keyManager" keyStore "github.com/Bit-Nation/panthalassa/keyStore" mnemonic "github.com/Bit-Nation/panthalassa/mnemonic" + otto "github.com/robertkrimen/otto" require "github.com/stretchr/testify/require" - duktape "gopkg.in/olebedev/go-duktape.v3" ) func TestModule_Register(t *testing.T) { @@ -26,11 +26,13 @@ func TestModule_Register(t *testing.T) { // create address module mod := New(km) - vm := duktape.New() + vm := otto.New() mod.Register(vm) - v := vm.ToString(0) - require.Equal(t, "0x748A6536dE0a8b1902f808233DD75ec4451cdFC6", v) + v, err := vm.Run(`ethereumAddress`) + require.Nil(t, err) + + require.Equal(t, "0x748A6536dE0a8b1902f808233DD75ec4451cdFC6", v.String()) } diff --git a/dapp/module/logger/module.go b/dapp/module/logger/module.go index 97639f4..76b3152 100644 --- a/dapp/module/logger/module.go +++ b/dapp/module/logger/module.go @@ -7,7 +7,7 @@ import ( log "github.com/ipfs/go-log" net "github.com/libp2p/go-libp2p-net" logger "github.com/op/go-logging" - duktape "gopkg.in/olebedev/go-duktape.v3" + otto "github.com/robertkrimen/otto" ) var sysLog = log.Logger("logger module logger") @@ -60,22 +60,21 @@ func (l *Logger) Close() error { // Register a module that writes console.log // to the given logger -func (l *Logger) Register(vm *duktape.Context) error { - //@TODO Find a way to overwrite method console.log if neccessary, so that we don't need to call consoleLog - _, err := vm.PushGlobalGoFunction("consoleLog", func(context *duktape.Context) int { - sysLog.Debug("write log statement") - toLog := []string{} - var i int - for { - if context.GetType(i).IsNone() { - break +func (l *Logger) Register(vm *otto.Otto) error { + + return vm.Set("console", map[string]interface{}{ + "log": func(call otto.FunctionCall) otto.Value { + + sysLog.Debug("write log statement") + + toLog := []string{} + for _, arg := range call.ArgumentList { + sysLog.Debug("log: ", toLog) + toLog = append(toLog, arg.String()) } - sysLog.Debug("log: ", toLog) - toLog = append(toLog, context.ToString(i)) - i++ - } - l.Logger.Info(strings.Join(toLog, ",")) - return 0 + l.Logger.Info(strings.Join(toLog, ",")) + return otto.Value{} + }, }) - return err + } diff --git a/dapp/module/logger/module_test.go b/dapp/module/logger/module_test.go index 20eff0f..fed6272 100644 --- a/dapp/module/logger/module_test.go +++ b/dapp/module/logger/module_test.go @@ -4,8 +4,8 @@ import ( "testing" logger "github.com/op/go-logging" + otto "github.com/robertkrimen/otto" require "github.com/stretchr/testify/require" - duktape "gopkg.in/olebedev/go-duktape.v3" ) type testValue struct { @@ -24,22 +24,21 @@ func (w testWriter) Write(b []byte) (int, error) { func TestLoggerModule(t *testing.T) { - //@TODO Find a way to overwrite method console.log if neccessary, so that we don't need to call consoleLog testValues := []testValue{ testValue{ - js: `consoleLog(1, 2, 3)`, + js: `console.log(1, 2, 3)`, assertion: func(consoleOut string) { require.Equal(t, "1,2,3\n", consoleOut) }, }, testValue{ - js: `consoleLog("hi","there")`, + js: `console.log("hi","there")`, assertion: func(consoleOut string) { require.Equal(t, "hi,there\n", consoleOut) }, }, testValue{ - js: `consoleLog({key: 4})`, + js: `console.log({key: 4})`, assertion: func(consoleOut string) { require.Equal(t, "[object Object]\n", consoleOut) }, @@ -47,14 +46,14 @@ func TestLoggerModule(t *testing.T) { testValue{ js: ` var cb = function(){}; - consoleLog(cb) + console.log(cb) `, assertion: func(consoleOut string) { - require.Equal(t, "function () { [ecmascript code] }\n", consoleOut) + require.Equal(t, "function(){}\n", consoleOut) }, }, testValue{ - js: `consoleLog("hi",1)`, + js: `console.log("hi",1)`, assertion: func(consoleOut string) { require.Equal(t, "hi,1\n", consoleOut) }, @@ -64,7 +63,7 @@ func TestLoggerModule(t *testing.T) { for _, testValue := range testValues { // create VM - vm := duktape.New() + vm := otto.New() // create logger b := logger.NewLogBackend(testWriter{ @@ -79,7 +78,7 @@ func TestLoggerModule(t *testing.T) { loggerModule.Logger = l loggerModule.Register(vm) - err = vm.PevalString((testValue.js)) + _, err = vm.Run(testValue.js) require.Nil(t, err) } diff --git a/dapp/module/message/message.go b/dapp/module/message/message.go index f88f1b7..361f9e4 100644 --- a/dapp/module/message/message.go +++ b/dapp/module/message/message.go @@ -6,14 +6,15 @@ import ( "errors" "time" + log "github.com/ipfs/go-log" + ed25519 "golang.org/x/crypto/ed25519" + reqLim "github.com/Bit-Nation/panthalassa/dapp/request_limitation" validator "github.com/Bit-Nation/panthalassa/dapp/validator" db "github.com/Bit-Nation/panthalassa/db" - log "github.com/ipfs/go-log" logger "github.com/op/go-logging" + otto "github.com/robertkrimen/otto" uuid "github.com/satori/go.uuid" - ed25519 "golang.org/x/crypto/ed25519" - duktape "gopkg.in/olebedev/go-duktape.v3" ) type Module struct { @@ -47,9 +48,9 @@ func (m *Module) Close() error { return nil } -func (m *Module) Register(vm *duktape.Context) error { - _, err := vm.PushGlobalGoFunction("sendMessage", func(context *duktape.Context) int { - var itemsToPopBeforeCallback int +func (m *Module) Register(vm *otto.Otto) error { + return vm.Set("sendMessage", func(call otto.FunctionCall) otto.Value { + sysLog.Debug("send message") // validate function call v := validator.New() @@ -59,27 +60,32 @@ func (m *Module) Register(vm *duktape.Context) error { v.Set(1, &validator.TypeObject) // callback v.Set(2, &validator.TypeFunction) + cb := call.Argument(2) // utils to handle an occurred error - handleError := func(errMsg string) int { - if context.IsFunction(2) { - context.PopN(itemsToPopBeforeCallback) - context.PushString(errMsg) - context.Call(1) - return 0 + handleError := func(errMsg string) otto.Value { + if cb.IsFunction() { + _, err := cb.Call(cb, errMsg) + if err != nil { + m.logger.Error(err.Error()) + } + return otto.Value{} } m.logger.Error(errMsg) - return 1 + return otto.Value{} } - if err := v.Validate(vm); err != nil { + if err := v.Validate(vm, call); err != nil { // in the case an callback has been passed we want to call it with the error - return handleError(err.Error()) + return handleError(err.String()) } + + messagePayload := call.Argument(1).Object() + objv := validator.NewObjValidator() objv.Set("shouldSend", validator.ObjTypeBool, true) objv.Set("params", validator.ObjTypeObject, false) objv.Set("type", validator.ObjTypeString, false) - if err := objv.Validate(vm, 1); err != nil { - return handleError(err.Error()) + if err := objv.Validate(vm, *messagePayload); err != nil { + return handleError(err.String()) } dAppMessage := db.DAppMessage{ @@ -88,8 +94,7 @@ func (m *Module) Register(vm *duktape.Context) error { } // chat in which the message should be persisted - chatStr := context.ToString(0) - itemsToPopBeforeCallback++ + chatStr := call.Argument(0).String() chat, err := hex.DecodeString(chatStr) if err != nil { return handleError(err.Error()) @@ -97,57 +102,69 @@ func (m *Module) Register(vm *duktape.Context) error { if len(chat) != 32 { return handleError("chat must be 32 bytes long") } + // should this message be sent or kept locally? - if !context.GetPropString(1, "shouldSend") { - handleError(`key "shouldSend" doesn't exist`) + shouldSendVal, err := messagePayload.Get("shouldSend") + if err != nil { + return handleError(err.Error()) } - dAppMessage.ShouldSend = context.ToBoolean(-1) - itemsToPopBeforeCallback++ - itemsToPopBeforeCallback++ + dAppMessage.ShouldSend, err = shouldSendVal.ToBoolean() + if err != nil { + return handleError(err.Error()) + } + // set optional type of the message - if !context.GetPropString(1, "type") { - handleError(`key "type" doesn't exist`) + if hasKey(messagePayload.Keys(), "type") { + msgType, err := messagePayload.Get("type") + if err != nil { + return handleError(err.Error()) + } + dAppMessage.Type = msgType.String() } - dAppMessage.Type = context.ToString(-1) - itemsToPopBeforeCallback++ - itemsToPopBeforeCallback++ + // set optional params - if !context.GetPropString(1, "params") { - handleError(`key "params" doesn't exist`) - } + if hasKey(messagePayload.Keys(), "params") { - //@TODO Find a way to iterate over nested object key / values - //@TODO Lets pass objects to VM in json format and handle them fully in golang as it's much more powerful? - //@TODO Figure out if Duktape has a way to iterate over object keys and values - if !context.GetPropString(-1, "key") { - handleError(`key "key" doesn't exist`) - } + // fetch params object + paramsObj, err := messagePayload.Get("params") + if err != nil { + return handleError(err.Error()) + } + params := paramsObj.Object() + + // transform params to map + for _, objKey := range params.Keys() { + vmValue, err := params.Get(objKey) + value, err := vmValue.Export() + if err != nil { + return handleError(err.Error()) + } + dAppMessage.Params[objKey] = value + } - vmValue := context.ToString(-1) - itemsToPopBeforeCallback++ - itemsToPopBeforeCallback++ - dAppMessage.Params["key"] = vmValue - // marshal params - marshaledParams, err := json.Marshal(dAppMessage.Params) - if err != nil { - return handleError(err.Error()) - } + // marshal params + marshaledParams, err := json.Marshal(dAppMessage.Params) + if err != nil { + return handleError(err.Error()) + } + + // make sure it's less than 64 KB + if len(marshaledParams) > 64*1024 { + return handleError("the message params can't be bigger than 64 kb") + } + if err := json.Unmarshal(marshaledParams, &dAppMessage.Params); err != nil { + return handleError(err.Error()) + } - // make sure it's less than 64 KB - if len(marshaledParams) > 64*1024 { - return handleError("the message params can't be bigger than 64 kb") - } - if err := json.Unmarshal(marshaledParams, &dAppMessage.Params); err != nil { - return handleError(err.Error()) } - throttlingFunc := func(dec chan struct{}, vmDone chan struct{}) { + // persist message + m.reqLim.Exec(func(dec chan struct{}) { defer func() { - <-vmDone dec <- struct{}{} }() - chat, err := m.chatStorage.GetChat(chat) + chat, err := m.chatStorage.GetChatByPartner(chat) if err != nil { handleError(err.Error()) return @@ -175,18 +192,12 @@ func (m *Module) Register(vm *duktape.Context) error { handleError(err.Error()) return } + if _, err := cb.Call(cb); err != nil { + m.logger.Error(err.Error()) + } + }) - // See https://duktape.org/api.html - // Each function description includes Stack : (No effect on value stack) or a description of the effect it has on the stack - // When we call functions which modify the stack, we need to Pop them in order for things to work as intended - context.PopN(itemsToPopBeforeCallback) - context.PushUndefined() - context.Call(1) - return - } - m.reqLim.Exec(throttlingFunc) - return 0 + return otto.Value{} }) - return err } diff --git a/dapp/module/message/message_test.go b/dapp/module/message/message_test.go index bdbd6c5..1b7cc42 100644 --- a/dapp/module/message/message_test.go +++ b/dapp/module/message/message_test.go @@ -9,14 +9,15 @@ import ( "testing" "time" + ed25519 "golang.org/x/crypto/ed25519" + db "github.com/Bit-Nation/panthalassa/db" km "github.com/Bit-Nation/panthalassa/keyManager" ks "github.com/Bit-Nation/panthalassa/keyStore" mnemonic "github.com/Bit-Nation/panthalassa/mnemonic" storm "github.com/asdine/storm" + otto "github.com/robertkrimen/otto" require "github.com/stretchr/testify/require" - ed25519 "golang.org/x/crypto/ed25519" - duktape "gopkg.in/olebedev/go-duktape.v3" ) func createStorm() *storm.DB { @@ -49,7 +50,7 @@ func createKeyManager() *km.KeyManager { func TestPersistMessageSuccessfully(t *testing.T) { - vm := duktape.New() + vm := otto.New() // dapp pub key dAppPubKey, _, err := ed25519.GenerateKey(rand.Reader) @@ -65,7 +66,7 @@ func TestPersistMessageSuccessfully(t *testing.T) { // storage mock chatStorage := db.NewChatStorage(createStorm(), []func(e db.MessagePersistedEvent){}, createKeyManager()) chatStorage.AddListener(func(e db.MessagePersistedEvent) { - chat, _ := chatStorage.GetChat(chatPubKey) + chat, _ := chatStorage.GetChatByPartner(chatPubKey) if err != nil { panic(err) } @@ -96,117 +97,32 @@ func TestPersistMessageSuccessfully(t *testing.T) { closer <- struct{}{} }) // create chat - require.Nil(t, chatStorage.CreateChat(chatPubKey)) + _, err = chatStorage.CreateChat(chatPubKey) + require.Nil(t, err) msgModule := New(chatStorage, dAppPubKey, nil) require.Nil(t, msgModule.Register(vm)) - msg := `({ + msg := map[string]interface{}{ "shouldSend": true, - "params": { + "params": map[string]interface{}{ "key": "value", }, "type": "SEND_MONEY", - })` - - _, err = vm.PushGlobalGoFunction("callbackSendMessage", func(context *duktape.Context) int { - if !context.IsUndefined(0) { - require.Fail(t, context.ToString(0)) - } - - return 0 - }) - require.Nil(t, err) - vm.PevalString(`sendMessage(` + `"` + hex.EncodeToString(chatPubKey) + `"` + `,` + msg + `,` + `callbackSendMessage)`) - select { - case <-closer: - case <-time.After(time.Second * 5): - require.Fail(t, "timed out") - } - -} - -func TestPersistInvalidFunctionCall(t *testing.T) { - - vm := duktape.New() - - // dapp pub key - dAppPubKey, _, err := ed25519.GenerateKey(rand.Reader) - require.Nil(t, err) - - // chat pub key - chatPubKey, _, err := ed25519.GenerateKey(rand.Reader) - require.Nil(t, err) - - msgModule := New(nil, dAppPubKey, nil) - require.Nil(t, msgModule.Register(vm)) - - closer := make(chan struct{}, 1) - - _, err = vm.PushGlobalGoFunction("callbackSendMessage", func(context *duktape.Context) int { - - err := context.ToString(0) - require.Equal(t, "ValidationError: expected parameter 1 to be of type object", err) - closer <- struct{}{} - return 0 - }) - require.Nil(t, err) - vm.PevalString(`sendMessage(` + `"` + hex.EncodeToString(chatPubKey) + `"` + `,` + `"nil"` + `,` + `callbackSendMessage)`) - select { - case <-closer: - case <-time.After(time.Second): - require.Fail(t, "timed out") } -} - -// test whats happening if the chat is too short -func TestPersistChatTooShort(t *testing.T) { + vm.Call("sendMessage", vm, hex.EncodeToString(chatPubKey), msg, func(call otto.FunctionCall) otto.Value { - vm := duktape.New() - - msgModule := New(nil, nil, nil) - require.Nil(t, msgModule.Register(vm)) - - closer := make(chan struct{}, 1) - - _, err := vm.PushGlobalGoFunction("callbackSendMessage", func(context *duktape.Context) int { - err := context.ToString(0) - require.Equal(t, "chat must be 32 bytes long", err) + err := call.Argument(0) + if err.IsDefined() { + require.Fail(t, err.String()) + } closer <- struct{}{} - return 0 - + return otto.Value{} }) - require.Nil(t, err) - vm.PevalString(`sendMessage(` + `"` + hex.EncodeToString(make([]byte, 16)) + `"` + `,` + `({"shouldSend": true})` + `,` + `callbackSendMessage)`) - select { - case <-closer: - case <-time.After(time.Second): - require.Fail(t, "timed out") - } - -} - -// test what happens if we try to send without -func TestPersistWithoutShouldSend(t *testing.T) { - vm := duktape.New() - - msgModule := New(nil, nil, nil) - require.Nil(t, msgModule.Register(vm)) - - closer := make(chan struct{}, 1) - - _, err := vm.PushGlobalGoFunction("callbackSendMessage", func(context *duktape.Context) int { - err := context.ToString(0) - require.Equal(t, "ValidationError: Missing value for required key: shouldSend", err) - closer <- struct{}{} - return 0 - }) - require.Nil(t, err) - vm.PevalString(`sendMessage(` + `"` + hex.EncodeToString(make([]byte, 16)) + `"` + `,` + `({})` + `,` + `callbackSendMessage)`) select { case <-closer: case <-time.After(time.Second): diff --git a/dapp/module/message/test_utils.js.go b/dapp/module/message/test_utils.js.go new file mode 100644 index 0000000..89f8dd3 --- /dev/null +++ b/dapp/module/message/test_utils.js.go @@ -0,0 +1,49 @@ +package message + +import ( + db "github.com/Bit-Nation/panthalassa/db" + ed25519 "golang.org/x/crypto/ed25519" +) + +type testMessageStorage struct { + persistMessageToSend func(to ed25519.PublicKey, msg db.Message) error + persistReceivedMessage func(partner ed25519.PublicKey, msg db.Message) error + updateStatus func(partner ed25519.PublicKey, msgID int64, newStatus db.Status) error + messages func(partner ed25519.PublicKey, start int64, amount uint) ([]db.Message, error) + allChats func() ([]ed25519.PublicKey, error) + addListener func(fn func(e db.MessagePersistedEvent)) + getMessage func(partner ed25519.PublicKey, messageID int64) (*db.Message, error) + persistDAppMessage func(partner ed25519.PublicKey, msg db.DAppMessage) error +} + +func (s *testMessageStorage) PersistMessageToSend(partner ed25519.PublicKey, msg db.Message) error { + return s.persistMessageToSend(partner, msg) +} + +func (s *testMessageStorage) UpdateStatus(partner ed25519.PublicKey, msgID int64, newStatus db.Status) error { + return s.updateStatus(partner, msgID, newStatus) +} + +func (s *testMessageStorage) PersistReceivedMessage(partner ed25519.PublicKey, msg db.Message) error { + return s.persistReceivedMessage(partner, msg) +} + +func (s *testMessageStorage) Messages(partner ed25519.PublicKey, start int64, amount uint) ([]db.Message, error) { + return s.messages(partner, start, amount) +} + +func (s *testMessageStorage) AllChats() ([]ed25519.PublicKey, error) { + return s.allChats() +} + +func (s *testMessageStorage) AddListener(fn func(e db.MessagePersistedEvent)) { + s.addListener(fn) +} + +func (s *testMessageStorage) GetMessage(partner ed25519.PublicKey, messageID int64) (*db.Message, error) { + return s.getMessage(partner, messageID) +} + +func (s *testMessageStorage) PersistDAppMessage(partner ed25519.PublicKey, msg db.DAppMessage) error { + return s.persistDAppMessage(partner, msg) +} diff --git a/dapp/module/modal/module.go b/dapp/module/modal/module.go index da97c3f..5f7252b 100644 --- a/dapp/module/modal/module.go +++ b/dapp/module/modal/module.go @@ -11,8 +11,8 @@ import ( reqLim "github.com/Bit-Nation/panthalassa/dapp/request_limitation" validator "github.com/Bit-Nation/panthalassa/dapp/validator" logger "github.com/op/go-logging" + otto "github.com/robertkrimen/otto" uuid "github.com/satori/go.uuid" - duktape "gopkg.in/olebedev/go-duktape.v3" ) type Device interface { @@ -23,12 +23,12 @@ var sysLog = log.Logger("modal") type addModalID struct { id string - closer *duktape.Context + closer *otto.Value } type fetchModalCloser struct { id string - respChan chan *duktape.Context + respChan chan *otto.Value } type Module struct { @@ -57,7 +57,7 @@ func New(l *logger.Logger, device Device, dAppIDKey ed25519.PublicKey) *Module { go func() { - modals := map[string]*duktape.Context{} + modals := map[string]*otto.Value{} // exit if channels are closed if m.addModalIDChan == nil || m.fetchModalCloserChan == nil || m.deleteModalID == nil { @@ -101,9 +101,10 @@ func (m *Module) Close() error { // the second parameter should be the layout to render // and the third parameter is an optional callback that // will called with an optional error -func (m *Module) Register(vm *duktape.Context) error { +func (m *Module) Register(vm *otto.Otto) error { + + err := vm.Set("renderModal", func(call otto.FunctionCall) otto.Value { - _, err := vm.PushGlobalGoFunction("renderModal", func(context *duktape.Context) int { sysLog.Debug("render modal") // validate function call @@ -114,16 +115,19 @@ func (m *Module) Register(vm *duktape.Context) error { v.Set(1, &validator.TypeString) // callback v.Set(2, &validator.TypeFunction) - if err := v.Validate(context); err != nil { - m.logger.Error(err.Error()) - return 1 + if err := v.Validate(vm, call); err != nil { + m.logger.Error(err.String()) + return otto.Value{} } // modal ui id - uiID := context.ToString(0) + uiID := call.Argument(0).String() + + // callback + cb := call.Argument(2) // make sure ui id is registered - mIdChan := make(chan *duktape.Context) + mIdChan := make(chan *otto.Value) m.fetchModalCloserChan <- fetchModalCloser{ id: uiID, respChan: mIdChan, @@ -132,35 +136,37 @@ func (m *Module) Register(vm *duktape.Context) error { // a modal closer can only exist with relation to a modal ID. // So if the modal closer exist the modal id exist as well if modalCloser == nil { - errMsg := fmt.Sprintf("MissingModalID: modal UI ID: '%s' does not exist", uiID) - context.PushString(errMsg) - context.PushString(uiID) - context.Call(2) - return 1 + errMsg := fmt.Sprintf("modal UI ID: '%s' does not exist", uiID) + if _, err := cb.Call(cb, vm.MakeCustomError("MissingModalID", errMsg), uiID); err != nil { + m.logger.Error(errMsg) + } + return otto.Value{} } // get layout - layout := context.ToString(1) + layout := call.Argument(1).String() sysLog.Debugf("going to render: %s with UI id %s", layout, uiID) // execute show modal action in // context of request limitation - // @TODO figure out why concurrently using "go func" instead of "func" makes the vm/context lose the stack - func() { + go func() { // request to show modal if err := m.device.RenderModal(uiID, layout, m.dAppIDKey); err != nil { - context.PushString(`Error: failed to render modal ` + err.Error()) - context.Call(1) + _, err = cb.Call(cb, vm.MakeCustomError("Error", "failed to render modal")) + if err != nil { + m.logger.Error(err.Error()) + } return } - context.PushUndefined() - context.Call(1) + if _, err := cb.Call(cb); err != nil { + m.logger.Error(err.Error()) + } }() - return 0 + return otto.Value{} }) @@ -168,63 +174,62 @@ func (m *Module) Register(vm *duktape.Context) error { return err } - _, err = vm.PushGlobalGoFunction("newModalUIID", func(context *duktape.Context) int { + return vm.Set("newModalUIID", func(call otto.FunctionCall) otto.Value { + // validate function call v := validator.New() // close handler v.Set(0, &validator.TypeFunction) // callback v.Set(1, &validator.TypeFunction) - if err := v.Validate(context); err != nil { - m.logger.Error(err.Error()) - return 1 + if err := v.Validate(vm, call); err != nil { + m.logger.Error(err.String()) + return otto.Value{} } + cb := call.Argument(1) + // create new id id, err := uuid.NewV4() if err != nil { - context.PushString("Error " + err.Error()) - context.Call(1) - return 1 + _, err = cb.Call(cb, vm.MakeCustomError("Error", err.Error())) + if err != nil { + m.logger.Error(err.Error()) + } + return otto.Value{} } // increase request limitation counter - throttlingFunc := func(dec chan struct{}, vmDone chan struct{}) { - defer func() { - <-vmDone - }() + m.modalIDsReqLim.Exec(func(dec chan struct{}) { + // add ui id & closer to stack + closer := call.Argument(0) m.addModalIDChan <- addModalID{ id: id.String(), - closer: context, + closer: &closer, } // call callback - context.PopN(0) - context.PushUndefined() - context.PushString(id.String()) - context.Call(2) - - //@TODO find a way to determine if the callback errored out - //if err != nil { - // in the case of an error we would like to remove the id - // and decrease our request limitation - // m.deleteModalID <- id.String() - // m.logger.Error(err.Error()) - //} - } - m.modalIDsReqLim.Exec(throttlingFunc) - return 0 + _, err = cb.Call(cb, nil, id.String()) + if err != nil { + // in the case of an error we would like to remove the id + // and decrease our request limitation + m.deleteModalID <- id.String() + m.logger.Error(err.Error()) + } + }) + + return otto.Value{} }) - return err + } // close the modal func (m *Module) CloseModal(uiID string) { // fetch closer - respChan := make(chan *duktape.Context) + respChan := make(chan *otto.Value) m.fetchModalCloserChan <- fetchModalCloser{ id: uiID, respChan: respChan, @@ -235,7 +240,9 @@ func (m *Module) CloseModal(uiID string) { } // close modal - closer.Call(0) + if _, err := closer.Call(*closer); err != nil { + m.logger.Error(err.Error()) + } // finish close (decrease counter & delete from id's") m.deleteModalID <- uiID diff --git a/dapp/module/modal/module_test.go b/dapp/module/modal/module_test.go index 7a9b5dd..763007a 100644 --- a/dapp/module/modal/module_test.go +++ b/dapp/module/modal/module_test.go @@ -5,10 +5,10 @@ import ( "time" log "github.com/op/go-logging" + otto "github.com/robertkrimen/otto" uuid "github.com/satori/go.uuid" require "github.com/stretchr/testify/require" ed25519 "golang.org/x/crypto/ed25519" - duktape "gopkg.in/olebedev/go-duktape.v3" ) type testDevice struct { @@ -24,45 +24,45 @@ func TestModule_CloseModal(t *testing.T) { // create modal module logger := log.MustGetLogger("") m := New(logger, nil, []byte("")) - vm := duktape.New() + vm := otto.New() require.Nil(t, m.Register(vm)) // closer closed := false - - _, err := vm.PushGlobalGoFunction("callbackCloserCloseModal", func(context *duktape.Context) int { + closer := func() { closed = true - return 0 - }) - require.Nil(t, err) + } closeTest := make(chan struct{}, 1) // create new uuid - _, err = vm.PushGlobalGoFunction("callbackTestModuleCloseModal", func(context *duktape.Context) int { + _, err := vm.Call("newModalUIID", vm, closer, func(call otto.FunctionCall) otto.Value { + // fetch callback data - errBool := context.IsUndefined(0) - modalID := context.ToString(1) + err := call.Argument(0) + modalID := call.Argument(1) // error must be undefined - require.True(t, errBool) + require.True(t, err.IsUndefined()) + // convert returned id to uuid - id, convertErr := uuid.FromString(modalID) + id, convertErr := uuid.FromString(modalID.String()) require.Nil(t, convertErr) - require.Equal(t, modalID, id.String()) + require.Equal(t, modalID.String(), id.String()) + // id must be registered in modal id map - respChan := make(chan *duktape.Context) + respChan := make(chan *otto.Value) m.fetchModalCloserChan <- fetchModalCloser{ id: id.String(), respChan: respChan, } require.NotNil(t, <-respChan) + // close modal - vm.PevalString(`callbackCloserCloseModal`) m.CloseModal(id.String()) // id must NOT be registered in modal id map - respChan = make(chan *duktape.Context) + respChan = make(chan *otto.Value) m.fetchModalCloserChan <- fetchModalCloser{ id: id.String(), respChan: respChan, @@ -72,11 +72,9 @@ func TestModule_CloseModal(t *testing.T) { // close test closeTest <- struct{}{} - return 0 + return otto.Value{} }) require.Nil(t, err) - err = vm.PevalString(`newModalUIID(callbackCloserCloseModal,callbackTestModuleCloseModal)`) - require.Nil(t, err) select { case <-closeTest: @@ -105,29 +103,29 @@ func TestModule_RenderModal(t *testing.T) { // create module logger := log.MustGetLogger("") m := New(logger, device, []byte("id pub key")) - vm := duktape.New() + vm := otto.New() require.Nil(t, m.Register(vm)) // we just register a fake it here to just // make sure that we have an ID in the vm m.addModalIDChan <- addModalID{ id: uiID, - closer: &duktape.Context{}, + closer: &otto.Value{}, } done := make(chan struct{}, 1) - _, err := vm.PushGlobalGoFunction("callbackTestModuleCloseModal", func(context *duktape.Context) int { + + _, err := vm.Call("renderModal", vm, uiID, "{jsx: 'tree'}", func(call otto.FunctionCall) otto.Value { + // make sure device has been called require.True(t, calledDevice) // close test done <- struct{}{} - return 0 + return otto.Value{} }) require.Nil(t, err) - err = vm.PevalString(`renderModal("` + uiID + `","{jsx: 'tree'}",callbackTestModuleCloseModal)`) - require.Nil(t, err) select { case <-done: @@ -142,19 +140,17 @@ func TestModal_RenderWithoutID(t *testing.T) { // create module logger := log.MustGetLogger("") m := New(logger, nil, []byte("id pub key")) - vm := duktape.New() + vm := otto.New() require.Nil(t, m.Register(vm)) done := make(chan struct{}, 1) - _, err := vm.PushGlobalGoFunction("callbackTestModalRenderWithoutID", func(context *duktape.Context) int { - err := context.ToString(0) - require.Equal(t, "MissingModalID: modal UI ID: 'id_do_not_exist' does not exist", err) + + vm.Call("renderModal", vm, "id_do_not_exist", "", func(call otto.FunctionCall) otto.Value { + err := call.Argument(0) + require.Equal(t, "MissingModalID: modal UI ID: 'id_do_not_exist' does not exist", err.String()) done <- struct{}{} - return 0 + return otto.Value{} }) - require.Nil(t, err) - err = vm.PevalString(`renderModal("id_do_not_exist", "", callbackTestModalRenderWithoutID)`) - require.Nil(t, err) select { case <-done: @@ -169,41 +165,39 @@ func TestModal_RequestLimitation(t *testing.T) { // create module logger := log.MustGetLogger("") m := New(logger, nil, []byte("id pub key")) - vm := duktape.New() + vm := otto.New() require.Nil(t, m.Register(vm)) - _, err := vm.PushGlobalGoFunction("callbackCloserRequestLimitation", func(context *duktape.Context) int { - return 0 - }) - require.Nil(t, err) + closer := func() otto.Value { + return otto.Value{} + } // make sure that current amount of registered ids is 0 require.Equal(t, uint(0), m.modalIDsReqLim.Current()) // closer done := make(chan struct{}, 1) - _, err = vm.PushGlobalGoFunction("callbackTestModalRequestLimitation", func(context *duktape.Context) int { + + vm.Call("newModalUIID", vm, closer, func(call otto.FunctionCall) otto.Value { + // newModalUIID must register a new id require.Equal(t, uint(1), m.modalIDsReqLim.Current()) // close modal with UI ID - id := context.ToString(1) - vm.PevalString(`callbackCloserRequestLimitation`) - m.CloseModal(id) + id := call.Argument(1) + m.CloseModal(id.String()) // wait a bit to sync go routines - time.Sleep(time.Millisecond * 100) + time.Sleep(time.Millisecond * 10) // make sure id was removed from current require.Equal(t, uint(0), m.modalIDsReqLim.Current()) done <- struct{}{} - return 0 + return otto.Value{} }) - require.Nil(t, err) - err = vm.PevalString(`newModalUIID(callbackCloserRequestLimitation,callbackTestModalRequestLimitation)`) - require.Nil(t, err) + select { case <-done: case <-time.After(time.Second * 3): diff --git a/dapp/module/module.go b/dapp/module/module.go index 5bbcb51..b24d093 100644 --- a/dapp/module/module.go +++ b/dapp/module/module.go @@ -1,10 +1,10 @@ package module import ( - duktape "gopkg.in/olebedev/go-duktape.v3" + otto "github.com/robertkrimen/otto" ) type Module interface { - Register(vm *duktape.Context) error + Register(vm *otto.Otto) error Close() error } diff --git a/dapp/module/randBytes/module.go b/dapp/module/randBytes/module.go index 923d12b..8e269ce 100644 --- a/dapp/module/randBytes/module.go +++ b/dapp/module/randBytes/module.go @@ -2,13 +2,11 @@ package randBytes import ( "crypto/rand" - "fmt" - "strings" validator "github.com/Bit-Nation/panthalassa/dapp/validator" log "github.com/ipfs/go-log" logger "github.com/op/go-logging" - duktape "gopkg.in/olebedev/go-duktape.v3" + otto "github.com/robertkrimen/otto" ) var randSource = rand.Reader @@ -29,46 +27,53 @@ func (m *Module) Close() error { return nil } -func (m *Module) Register(vm *duktape.Context) error { - _, err := vm.PushGlobalGoFunction("randomBytes", func(context *duktape.Context) int { +func (m *Module) Register(vm *otto.Otto) error { + return vm.Set("randomBytes", func(call otto.FunctionCall) otto.Value { + sysLog.Debug("generate random bytes") - var itemsToPopBeforeCallback int + // validate call v := validator.New() v.Set(0, &validator.TypeNumber) v.Set(1, &validator.TypeFunction) - handleError := func(errMsg string) int { - if context.IsFunction(1) { - context.PopN(itemsToPopBeforeCallback) - context.PushString(errMsg) - context.Call(1) - return 0 - } - m.logger.Error(errMsg) - return 1 - } - if err := v.Validate(context); err != nil { - m.logger.Error(err.Error()) + if err := v.Validate(vm, call); err != nil { + m.logger.Error(err.String()) } + cb := call.Argument(1) + // convert to integer - amount := context.ToInt(0) + amount, err := call.Argument(0).ToInteger() + if err != nil { + _, err := cb.Call(cb, err.Error()) + if err != nil { + m.logger.Error(err.Error()) + } + return otto.Value{} + } + + // read random bytes destination := make([]byte, amount) - _, err := randSource.Read(destination) + _, err = randSource.Read(destination) if err != nil { - handleError(err.Error()) - return 1 - } // if err != nil { + _, err := cb.Call(cb, err.Error()) + if err != nil { + m.logger.Error(err.Error()) + } + return otto.Value{} + } // call callback - context.PopN(itemsToPopBeforeCallback) - context.PushUndefined() - byteString := fmt.Sprint(destination) - jsFriendlyString := strings.Replace(strings.TrimSuffix(strings.TrimPrefix(byteString, "["), "]"), " ", ", ", -1) - context.PushString(jsFriendlyString) - context.Call(2) - return 0 - }) + _, err = cb.Call(cb, nil, destination) + if err != nil { + _, err := cb.Call(cb, err.Error()) + if err != nil { + m.logger.Error(err.Error()) + } + return otto.Value{} + } - return err + return otto.Value{} + + }) } diff --git a/dapp/module/randBytes/module_test.go b/dapp/module/randBytes/module_test.go index c7faa55..fcbc280 100644 --- a/dapp/module/randBytes/module_test.go +++ b/dapp/module/randBytes/module_test.go @@ -5,8 +5,8 @@ import ( "testing" log "github.com/op/go-logging" + otto "github.com/robertkrimen/otto" require "github.com/stretchr/testify/require" - duktape "gopkg.in/olebedev/go-duktape.v3" ) func TestCreateRandomBytes(t *testing.T) { @@ -15,23 +15,28 @@ func TestCreateRandomBytes(t *testing.T) { mod := New(log.MustGetLogger("")) - vm := duktape.New() + vm := otto.New() require.Nil(t, mod.Register(vm)) - vm.PushGlobalGoFunction("callbackRandomBytes", func(context *duktape.Context) int { - if !context.IsUndefined(0) { + _, err := vm.Call("randomBytes", vm, 3, func(call otto.FunctionCall) otto.Value { + + if !call.Argument(0).IsUndefined() { panic("expected error to be undefined") } - generatedBytes := (context.ToString(-1)) + generatedBytes, err := call.Argument(1).ToString() + if err != nil { + panic(err) + } + if generatedBytes != "1, 4, 6" { - panic("it's not the same bytes") + panic(err) } - return 0 + return otto.Value{} + }) - err := vm.PevalString(`randomBytes(3,callbackRandomBytes)`) require.Nil(t, err) } diff --git a/dapp/module/renderer/dapp/module.go b/dapp/module/renderer/dapp/module.go index be4cea5..e145912 100644 --- a/dapp/module/renderer/dapp/module.go +++ b/dapp/module/renderer/dapp/module.go @@ -6,14 +6,14 @@ import ( validator "github.com/Bit-Nation/panthalassa/dapp/validator" log "github.com/ipfs/go-log" logger "github.com/op/go-logging" - duktape "gopkg.in/olebedev/go-duktape.v3" + otto "github.com/robertkrimen/otto" ) type Module struct { logger *logger.Logger - vm *duktape.Context - setOpenHandlerChan chan *duktape.Context - getOpenHandlerChan chan chan *duktape.Context + vm *otto.Otto + setOpenHandlerChan chan *otto.Value + getOpenHandlerChan chan chan *otto.Value addCBChan chan *chan error rmCBChan chan *chan error // returns a cb chan from the stack @@ -23,38 +23,38 @@ type Module struct { var sysLog = log.Logger("renderer - dapp") -// setOpenHandler must be called with a callback -// the callback that is passed to `setOpenHandler` -// will be called with an "data" object and a callback -// the callback should be called (with an optional error) -// in order to return from the function -func (m *Module) Register(vm *duktape.Context) error { +func (m *Module) Register(vm *otto.Otto) error { m.vm = vm - _, err := vm.PushGlobalGoFunction("setOpenHandler", func(context *duktape.Context) int { + // setOpenHandler must be called with a callback + // the callback that is passed to `setOpenHandler` + // will be called with an "data" object and a callback + // the callback should be called (with an optional error) + // in order to return from the function + return vm.Set("setOpenHandler", func(call otto.FunctionCall) otto.Value { - sysLog.Debug("set open handler") + //sysLog.Debug("set open handler") // validate function call v := validator.New() v.Set(0, &validator.TypeFunction) - if err := v.Validate(context); err != nil { - m.logger.Error(err.Error()) - return 1 + if err := v.Validate(vm, call); err != nil { + m.logger.Error(err.String()) + return *err } // set renderer - m.setOpenHandlerChan <- context + fn := call.Argument(0) + m.setOpenHandlerChan <- &fn - return 0 + return otto.Value{} }) - return err } // payload can be an arbitrary set of key value pairs (as a json string) func (m *Module) OpenDApp(payload string) error { // fetch handler - handlerChan := make(chan *duktape.Context) + handlerChan := make(chan *otto.Value) m.getOpenHandlerChan <- handlerChan handler := <-handlerChan @@ -64,7 +64,10 @@ func (m *Module) OpenDApp(payload string) error { } // convert context to otto js object - dataObj := "(" + payload + ")" + dataObj, err := m.vm.Object("(" + payload + ")") + if err != nil { + return err + } cbDone := make(chan error) @@ -75,39 +78,31 @@ func (m *Module) OpenDApp(payload string) error { // call the renderer // we pass in data object and a callback - _, err := handler.PushGlobalGoFunction("callbackOpenDApp", func(context *duktape.Context) int { + _, err = handler.Call(*handler, dataObj, func(call otto.FunctionCall) otto.Value { // remove cb chan from state defer func() { m.rmCBChan <- &cbDone }() + // fetch params from the callback call + err := call.Argument(0) + // if there is an error, set it in the response - if !context.IsUndefined(0) && !context.IsNull(0) { - callbackerr := context.ToString(0) - cbDone <- errors.New(callbackerr) - return 1 + if !err.IsUndefined() && !err.IsNull() { + cbDone <- errors.New(err.String()) + return otto.Value{} } cbDone <- nil - return 0 + return otto.Value{} }) if err != nil { m.logger.Error(err.Error()) } - err = handler.PevalString(dataObj) - if err != nil { - m.logger.Error(err.Error()) - } - err = handler.PevalString(`callbackOpenDApp`) - if err != nil { - m.logger.Error(err.Error()) - } - - handler.Call(2) }() @@ -123,8 +118,8 @@ func New(l *logger.Logger) *Module { m := &Module{ logger: l, - setOpenHandlerChan: make(chan *duktape.Context), - getOpenHandlerChan: make(chan chan *duktape.Context), + setOpenHandlerChan: make(chan *otto.Value), + getOpenHandlerChan: make(chan chan *otto.Value), addCBChan: make(chan *chan error), rmCBChan: make(chan *chan error), nextCBChan: make(chan chan *chan error), @@ -133,7 +128,7 @@ func New(l *logger.Logger) *Module { go func() { - openHandler := new(duktape.Context) + openHandler := new(otto.Value) cbChans := map[*chan error]bool{} for { diff --git a/dapp/module/renderer/dapp/module_test.go b/dapp/module/renderer/dapp/module_test.go index f3d90b9..7d12c8b 100644 --- a/dapp/module/renderer/dapp/module_test.go +++ b/dapp/module/renderer/dapp/module_test.go @@ -4,45 +4,37 @@ import ( "testing" log "github.com/op/go-logging" + otto "github.com/robertkrimen/otto" require "github.com/stretchr/testify/require" - duktape "gopkg.in/olebedev/go-duktape.v3" ) func TestModule_OpenDAppError(t *testing.T) { l := log.MustGetLogger("") - vm := duktape.New() + vm := otto.New() m := New(l) require.Nil(t, m.Register(vm)) - _, err := vm.PushGlobalGoFunction("callbackTestModuleOpenDAppError", func(context *duktape.Context) int { + vm.Call("setOpenHandler", vm, func(context otto.Value, cb otto.Value) otto.Value { - if !context.IsObject(0) { - panic("callbackTestFuncCallSuccess : 0 is not an object") + v, err := context.Object().Get("key") + if err != nil { + panic(err) } - if !context.GetPropString(0, "key") { - panic("callbackTestFuncCallSuccess : key missing") - } - - v := context.ToString(-1) - if v != "value" { + if v.String() != "value" { panic("Expected value of key to be: value") } - context.Pop() - context.PushString("I am an error") - context.Call(1) + cb.Call(cb, "I am an error") + + return otto.Value{} - return 0 }) - require.Nil(t, err) - err = vm.PevalString(`setOpenHandler(callbackTestModuleOpenDAppError)`) - require.Nil(t, err) - vm.PevalString(`callbackTestModuleOpenDAppError`) - err = m.OpenDApp(`{key: "value"}`) + + err := m.OpenDApp(`{key: "value"}`) require.EqualError(t, err, "I am an error") } @@ -51,38 +43,29 @@ func TestModule_OpenDAppSuccess(t *testing.T) { l := log.MustGetLogger("") - vm := duktape.New() + vm := otto.New() m := New(l) require.Nil(t, m.Register(vm)) - _, err := vm.PushGlobalGoFunction("callbackTestModuleOpenDAppSuccess", func(context *duktape.Context) int { + vm.Call("setOpenHandler", vm, func(context otto.Value, cb otto.Value) otto.Value { - if !context.IsObject(0) { - panic("callbackTestFuncCallSuccess : 0 is not an object") + v, err := context.Object().Get("key") + if err != nil { + panic(err) } - if !context.GetPropString(0, "key") { - panic("callbackTestFuncCallSuccess : key missing") - } - - v := context.ToString(-1) - if v != "value" { + if v.String() != "value" { panic("Expected value of key to be: value") } - context.Pop() - context.PushUndefined() - context.Call(1) + cb.Call(cb, nil) + + return otto.Value{} - return 0 }) - require.Nil(t, err) - err = vm.PevalString(`setOpenHandler(callbackTestModuleOpenDAppSuccess)`) - require.Nil(t, err) - err = vm.PevalString(`callbackTestModuleOpenDAppSuccess`) - require.Nil(t, err) - err = m.OpenDApp(`{key: "value"}`) + + err := m.OpenDApp(`{key: "value"}`) require.Nil(t, err) } @@ -90,19 +73,17 @@ func TestModule_OpenDAppSuccess(t *testing.T) { func TestModule_Close(t *testing.T) { // setup - vm := duktape.New() + vm := otto.New() m := New(nil) require.Nil(t, m.Register(vm)) // set fake open handler - _, err := vm.PushGlobalGoFunction("callbackTestModuleClose", func(context *duktape.Context) int { + _, err := vm.Call("setOpenHandler", vm, func(call otto.FunctionCall) otto.Value { m.Close() - return 0 + return otto.Value{} }) require.Nil(t, err) - err = vm.PevalString(`setOpenHandler(callbackTestModuleClose)`) - require.Nil(t, err) - vm.PevalString(`callbackTestModuleClose`) + require.EqualError(t, m.OpenDApp("{}"), "closed the application") } diff --git a/dapp/module/renderer/message/module.go b/dapp/module/renderer/message/module.go index f96b926..5294d37 100644 --- a/dapp/module/renderer/message/module.go +++ b/dapp/module/renderer/message/module.go @@ -6,14 +6,14 @@ import ( validator "github.com/Bit-Nation/panthalassa/dapp/validator" log "github.com/ipfs/go-log" logger "github.com/op/go-logging" - duktape "gopkg.in/olebedev/go-duktape.v3" + otto "github.com/robertkrimen/otto" ) type Module struct { logger *logger.Logger - vm *duktape.Context - setRendererChan chan *duktape.Context - getRendererChan chan chan *duktape.Context + vm *otto.Otto + setRendererChan chan *otto.Value + getRendererChan chan chan *otto.Value addCBChan chan *chan resp rmCBChan chan *chan resp closer chan struct{} @@ -30,25 +30,26 @@ var sysLog = log.Logger("renderer - message") // 2. The "callback" should be can be called with two parameters: // 1. an error // 2. the rendered layout -func (m *Module) Register(vm *duktape.Context) error { +func (m *Module) Register(vm *otto.Otto) error { m.vm = vm - _, err := vm.PushGlobalGoFunction("setMessageRenderer", func(context *duktape.Context) int { + return vm.Set("setMessageRenderer", func(call otto.FunctionCall) otto.Value { + sysLog.Debug("set message renderer") // validate function call v := validator.New() v.Set(0, &validator.TypeFunction) - if err := v.Validate(context); err != nil { - m.logger.Error(err.Error()) - return 1 + if err := v.Validate(vm, call); err != nil { + m.logger.Error(err.String()) + return *err } // set renderer - m.setRendererChan <- context + fn := call.Argument(0) + m.setRendererChan <- &fn - return 0 + return otto.Value{} }) - return err } type resp struct { @@ -59,18 +60,22 @@ type resp struct { // payload can be an arbitrary set of key value pairs // should contain the "message" and the "context" tho func (m *Module) RenderMessage(payload string) (string, error) { + // fetch renderer - rendererChan := make(chan *duktape.Context) + rendererChan := make(chan *otto.Value) m.getRendererChan <- rendererChan renderer := <-rendererChan // make sure an renderer has been set - if renderer == nil { + if renderer == nil || !renderer.IsDefined() { return "", errors.New("failed to render message - no open handler set") } // convert context to otto js object - payloadObj := "(" + payload + ")" + payloadObj, err := m.vm.Object("(" + payload + ")") + if err != nil { + return "", err + } cbChan := make(chan resp, 1) m.addCBChan <- &cbChan @@ -79,7 +84,7 @@ func (m *Module) RenderMessage(payload string) (string, error) { // call the message renderer // @todo what happens if we call the callback twice? - _, err := renderer.PushGlobalGoFunction("callbackRenderMessage", func(context *duktape.Context) int { + _, err = renderer.Call(*renderer, payloadObj, func(call otto.FunctionCall) otto.Value { // delete cb chan from stack when we are done defer func() { @@ -87,38 +92,31 @@ func (m *Module) RenderMessage(payload string) (string, error) { }() // fetch params from the callback call + err := call.Argument(0) + layout := call.Argument(1) + r := resp{} // if there is an error, set it in the response - if !context.IsUndefined(0) && !context.IsNull(0) { - callbackerr := context.ToString(0) - r.error = errors.New(callbackerr) + if !err.IsUndefined() && !err.IsNull() { + r.error = errors.New(err.String()) } // set the layout in the response - if context.IsString(1) { - layout := context.ToString(1) - r.layout = layout + if layout.IsString() { + r.layout = layout.String() } cbChan <- r - return 0 + return otto.Value{} }) - if err != nil { - m.logger.Error(err.Error()) - } - err = renderer.PevalString(payloadObj) - if err != nil { - m.logger.Error(err.Error()) - } - err = renderer.PevalString(`callbackRenderMessage`) + if err != nil { m.logger.Error(err.Error()) } - renderer.Call(2) }() r := <-cbChan @@ -133,8 +131,8 @@ func (m *Module) Close() error { func New(l *logger.Logger) *Module { m := &Module{ logger: l, - setRendererChan: make(chan *duktape.Context), - getRendererChan: make(chan chan *duktape.Context), + setRendererChan: make(chan *otto.Value), + getRendererChan: make(chan chan *otto.Value), addCBChan: make(chan *chan resp), rmCBChan: make(chan *chan resp), closer: make(chan struct{}), @@ -142,7 +140,7 @@ func New(l *logger.Logger) *Module { go func() { - renderer := new(duktape.Context) + renderer := new(otto.Value) cbChans := map[*chan resp]bool{} for { diff --git a/dapp/module/renderer/message/module_test.go b/dapp/module/renderer/message/module_test.go index 4bc5768..aefd903 100644 --- a/dapp/module/renderer/message/module_test.go +++ b/dapp/module/renderer/message/module_test.go @@ -4,29 +4,28 @@ import ( "testing" log "github.com/op/go-logging" + otto "github.com/robertkrimen/otto" require "github.com/stretchr/testify/require" - duktape "gopkg.in/olebedev/go-duktape.v3" ) func TestModule_RenderMessageError(t *testing.T) { l := log.MustGetLogger("") - vm := duktape.New() + vm := otto.New() m := New(l) require.Nil(t, m.Register(vm)) - _, err := vm.PushGlobalGoFunction("callbackTestModuleRenderMessageError", func(context *duktape.Context) int { - context.PushString("I am an error") - context.Call(1) - return 0 + + vm.Call("setMessageRenderer", vm, func(payload otto.Value, cb otto.Value) otto.Value { + + cb.Call(cb, "I am an error") + + return otto.Value{} + }) - require.Nil(t, err) - err = vm.PevalString("setMessageRenderer(callbackTestModuleRenderMessageError)") - require.Nil(t, err) - vm.PevalString(`callbackTestModuleRenderMessageError`) - _, err = m.RenderMessage(`{}`) + _, err := m.RenderMessage(`{}`) require.EqualError(t, err, "I am an error") } @@ -35,35 +34,28 @@ func TestModule_RenderMessageSuccess(t *testing.T) { l := log.MustGetLogger("") - vm := duktape.New() + vm := otto.New() m := New(l) require.Nil(t, m.Register(vm)) - _, err := vm.PushGlobalGoFunction("callbackTestModuleRenderMessageSuccess", func(context *duktape.Context) int { - if !context.IsObject(0) { - panic("callbackTestFuncCallSuccess : 0 is not an object") - } - if !context.GetPropString(0, "message") { - panic("callbackTestFuncCallSuccess : message missing") + vm.Call("setMessageRenderer", vm, func(payload otto.Value, cb otto.Value) otto.Value { + + msg, err := payload.Object().Get("message") + if err != nil { + panic(err) } - if !context.IsObject(-1) { + if !msg.IsObject() { panic("Expected message to be an object") } - context.Pop() - context.PushUndefined() - context.PushString(`{}`) - context.Call(2) + cb.Call(cb, nil, "{}") - return 0 + return otto.Value{} }) - require.Nil(t, err) - err = vm.PevalString("setMessageRenderer(callbackTestModuleRenderMessageSuccess)") - require.Nil(t, err) - vm.PevalString(`callbackTestModuleRenderMessageSuccess`) + layout, err := m.RenderMessage(`{message: {}, context: {}}`) require.Nil(t, err) require.Equal(t, "{}", layout) @@ -72,19 +64,16 @@ func TestModule_RenderMessageSuccess(t *testing.T) { func TestModule_Close(t *testing.T) { - vm := duktape.New() + vm := otto.New() m := New(log.MustGetLogger("")) require.Nil(t, m.Register(vm)) - _, err := vm.PushGlobalGoFunction("callbackTestModuleClose", func(context *duktape.Context) int { + vm.Call("setMessageRenderer", vm, func(call otto.FunctionCall) otto.Value { m.Close() - return 0 + return otto.Value{} }) - require.Nil(t, err) - err = vm.PevalString(`setMessageRenderer(callbackTestModuleClose)`) - require.Nil(t, err) - vm.PevalString(`callbackTestModuleClose`) - _, err = m.RenderMessage("{}") + + _, err := m.RenderMessage("{}") require.EqualError(t, err, "closed the application") } diff --git a/dapp/module/sendEthTx/module.go b/dapp/module/sendEthTx/module.go index 95cb500..0abb46b 100644 --- a/dapp/module/sendEthTx/module.go +++ b/dapp/module/sendEthTx/module.go @@ -8,7 +8,7 @@ import ( validator "github.com/Bit-Nation/panthalassa/dapp/validator" log "github.com/ipfs/go-log" logger "github.com/op/go-logging" - duktape "gopkg.in/olebedev/go-duktape.v3" + otto "github.com/robertkrimen/otto" ) var sysLogger = log.Logger("send eth tx") @@ -37,104 +37,84 @@ func (m *Module) Close() error { return nil } -func (m *Module) Register(vm *duktape.Context) error { +func (m *Module) Register(vm *otto.Otto) error { // send an ethereum transaction // musst be called with an object that holds value, to and data - _, err := vm.PushGlobalGoFunction("sendETHTransaction", func(context *duktape.Context) int { - var itemsToPopBeforeCallback int + return vm.Set("sendETHTransaction", func(call otto.FunctionCall) otto.Value { + sysLogger.Debug("send eth transaction") // validate function call v := validator.New() v.Set(0, &validator.TypeObject) v.Set(1, &validator.TypeFunction) - // utils to handle an occurred error - handleError := func(errMsg string) int { - if context.IsFunction(1) { - context.PopN(itemsToPopBeforeCallback) - context.PushString(errMsg) - context.Call(1) - return 0 - } - m.logger.Error(errMsg) - return 1 + if err := v.Validate(vm, call); err != nil { + m.logger.Error(err.String()) + return *err } - if err := v.Validate(context); err != nil { - m.logger.Error(err.Error()) - return 1 - } + cb := call.Argument(1) // validate transaction object + obj := call.Argument(0).Object() objVali := validator.NewObjValidator() objVali.Set("value", validator.ObjTypeString, true) objVali.Set("to", validator.ObjTypeAddress, true) objVali.Set("data", validator.ObjTypeString, true) - if err := objVali.Validate(vm, 0); err != nil { - handleError(err.Error()) - return 1 + if err := objVali.Validate(vm, *obj); err != nil { + cb.Call(cb, err.String()) + return otto.Value{} } + // execute in the context of the throttling // request limitation - throttlingFunc := func(vmDone chan struct{}) { - defer func() { - <-vmDone - }() - if !context.GetPropString(0, "to") { - err := errors.New(`key "to" doesn't exist`) - handleError(err.Error()) + m.throttling.Exec(func() { + + to, err := obj.Get("to") + if err != nil { + if _, err := cb.Call(cb, err.Error()); err != nil { + m.logger.Error(err.Error()) + } return } - to := context.ToString(-1) - itemsToPopBeforeCallback++ - itemsToPopBeforeCallback++ - if !context.GetPropString(0, "value") { - err := errors.New(`key "value" doesn't exist`) - handleError(err.Error()) + value, err := obj.Get("value") + if err != nil { + if _, err := cb.Call(cb, err.Error()); err != nil { + m.logger.Error(err.Error()) + } return } - value := context.ToString(-1) - itemsToPopBeforeCallback++ - itemsToPopBeforeCallback++ - if !context.GetPropString(0, "data") { - err := errors.New(`key "data" doesn't exist`) - handleError(err.Error()) + data, err := obj.Get("data") + if err != nil { + if _, err := cb.Call(cb, err.Error()); err != nil { + m.logger.Error(err.Error()) + } return } - data := context.ToString(-1) - itemsToPopBeforeCallback++ - itemsToPopBeforeCallback++ // try to sign a transaction tx, err := m.ethApi.SendEthereumTransaction( - value, - to, - data, + value.String(), + to.String(), + data.String(), ) // exit on error if err != nil { - handleError(err.Error()) + cb.Call(cb, err.Error()) + return } // call callback with transaction - // See https://duktape.org/api.html - // Each function description includes Stack : (No effect on value stack) or a description of the effect it has on the stack - // When we call functions which modify the stack, we need to Pop them in order for things to work as intended - context.PopN(itemsToPopBeforeCallback) - context.PushUndefined() - context.PushString(tx) - context.Call(2) - return + cb.Call(cb, nil, tx) - } - m.throttling.Exec(throttlingFunc) + }) - return 0 + return otto.Value{} }) - return err + } diff --git a/dapp/module/sendEthTx/module_test.go b/dapp/module/sendEthTx/module_test.go index 3310243..78bca9d 100644 --- a/dapp/module/sendEthTx/module_test.go +++ b/dapp/module/sendEthTx/module_test.go @@ -5,8 +5,8 @@ import ( "time" log "github.com/op/go-logging" + otto "github.com/robertkrimen/otto" require "github.com/stretchr/testify/require" - duktape "gopkg.in/olebedev/go-duktape.v3" ) type testApi struct { @@ -21,7 +21,7 @@ func TestSuccessCall(t *testing.T) { logger := log.MustGetLogger("") - vm := duktape.New() + vm := otto.New() m := New(&testApi{ send: func(value, to, data string) (string, error) { @@ -40,33 +40,35 @@ func TestSuccessCall(t *testing.T) { require.Nil(t, m.Register(vm)) - txReq := `({ - "value": "10000", + txReq, err := vm.Object(`({ + "value": "10000" "to": "0xee60a19d0850b51b8598ca2ceb9acae3f452943d", "data": "0xf3..." - })` + })`) + require.Nil(t, err) wait := make(chan bool) - _, err := vm.PushGlobalGoFunction("callbackSendETHTransaction", func(context *duktape.Context) int { - - if !context.IsUndefined(0) { - panic("expected error to be undefined") - } + _, err = vm.Call( + "sendETHTransaction", + vm, + txReq, + func(error, transaction string) otto.Value { - transaction := (context.ToString(-1)) - if transaction != "{}" { - // it's ok to assert {} - // this is just a mock - panic("expected transaction to be {}") - } + if error != "undefined" { + panic("expected error to be undefined") + } - wait <- true - return 0 - }) - require.Nil(t, err) + if transaction != "{}" { + // it's ok to assert {} + // this is just a mock + panic("expected transaction to be {}") + } - go vm.PevalString(`sendETHTransaction(` + txReq + `,callbackSendETHTransaction)`) + wait <- true + return otto.Value{} + }, + ) require.Nil(t, err) select { diff --git a/dapp/module/uuidv4/module.go b/dapp/module/uuidv4/module.go index 0df492f..3a07ba1 100644 --- a/dapp/module/uuidv4/module.go +++ b/dapp/module/uuidv4/module.go @@ -1,12 +1,11 @@ package uuidv4 import ( - log "github.com/ipfs/go-log" - validator "github.com/Bit-Nation/panthalassa/dapp/validator" + log "github.com/ipfs/go-log" logger "github.com/op/go-logging" + otto "github.com/robertkrimen/otto" uuid "github.com/satori/go.uuid" - duktape "gopkg.in/olebedev/go-duktape.v3" ) var newUuid = uuid.NewV4 @@ -29,37 +28,42 @@ func (r *UUIDV4) Close() error { return nil } -func (r *UUIDV4) Register(vm *duktape.Context) error { +func (r *UUIDV4) Register(vm *otto.Otto) error { - _, err := vm.PushGlobalGoFunction("uuidV4", func(context *duktape.Context) int { + return vm.Set("uuidV4", func(call otto.FunctionCall) otto.Value { sysLogger.Debug("generate uuidv4") // validate function call v := validator.New() v.Set(0, &validator.TypeFunction) - if err := v.Validate(context); err != nil { - //vm.Run(`throw new Error("uuidV4 expects an callback as it's parameter")`) - return 1 + if err := v.Validate(vm, call); err != nil { + vm.Run(`throw new Error("uuidV4 expects an callback as it's parameter")`) + return otto.Value{} } + cb := call.Argument(0) + // create uuid id, err := newUuid() + // call callback with error if err != nil { - context.PushString(err.Error()) - context.PushUndefined() - context.Call(2) - return 1 + _, err = cb.Call(call.This, err.Error()) + if err != nil { + r.logger.Error(err.Error()) + } + return otto.Value{} } // call callback with uuid - context.PushUndefined() - context.PushString(id.String()) - context.Call(2) + _, err = cb.Call(call.This, nil, id.String()) + if err != nil { + r.logger.Error(err.Error()) + } - return 0 + return otto.Value{} }) - return err + } diff --git a/dapp/module/uuidv4/module_test.go b/dapp/module/uuidv4/module_test.go index 03ab298..228b14c 100644 --- a/dapp/module/uuidv4/module_test.go +++ b/dapp/module/uuidv4/module_test.go @@ -6,9 +6,9 @@ import ( "time" log "github.com/op/go-logging" + otto "github.com/robertkrimen/otto" uuid "github.com/satori/go.uuid" require "github.com/stretchr/testify/require" - duktape "gopkg.in/olebedev/go-duktape.v3" ) func TestUUIDV4ModelSuccess(t *testing.T) { @@ -20,19 +20,16 @@ func TestUUIDV4ModelSuccess(t *testing.T) { return uuid.FromString("9b781c39-2bd3-41c6-a246-150a9f4421a3") } - vm := duktape.New() - - require.Nil(t, m.Register(vm)) - - _, err := vm.PushGlobalGoFunction("test", func(context *duktape.Context) int { - require.True(t, context.IsUndefined(0)) - require.Equal(t, "9b781c39-2bd3-41c6-a246-150a9f4421a3", context.ToString(1)) - return 0 + vm := otto.New() + vm.Set("test", func(call otto.FunctionCall) otto.Value { + require.True(t, call.Argument(0).IsUndefined()) + require.Equal(t, "9b781c39-2bd3-41c6-a246-150a9f4421a3", call.Argument(1).String()) + return otto.Value{} }) - require.Nil(t, err) + require.Nil(t, m.Register(vm)) - vm.PevalString(`uuidV4(test)`) + vm.Run(`uuidV4(test)`) } @@ -45,28 +42,28 @@ func TestUUIDV4ModelError(t *testing.T) { return uuid.UUID{}, errors.New("I am a nice error message") } - vm := duktape.New() + vm := otto.New() require.Nil(t, m.Register(vm)) wait := make(chan bool, 1) - _, err := vm.PushGlobalGoFunction("callbackUuidV4", func(context *duktape.Context) int { - if context.ToString(0) != "I am a nice error message" { + vm.Call("uuidV4", vm, func(call otto.FunctionCall) otto.Value { + + if call.Argument(0).String() != "I am a nice error message" { panic("expected error message: I am a nice error message") } - if !context.IsUndefined(1) { + if !call.Argument(1).IsUndefined() { panic("id should be undefined") } wait <- true - return 0 + return otto.Value{} + }) - require.Nil(t, err) - vm.PevalString("uuidV4(callbackUuidV4)") select { case <-wait: case <-time.After(time.Second): diff --git a/dapp/validator/call.go b/dapp/validator/call.go index fc24420..498b487 100644 --- a/dapp/validator/call.go +++ b/dapp/validator/call.go @@ -6,34 +6,34 @@ import ( "sort" "sync" - duktape "gopkg.in/olebedev/go-duktape.v3" + otto "github.com/robertkrimen/otto" ) -type Validator = func(context *duktape.Context, position int) error +type Validator = func(call otto.FunctionCall, position int) error -var TypeFunction = func(context *duktape.Context, position int) error { - if !context.IsFunction(position) { +var TypeFunction = func(call otto.FunctionCall, position int) error { + if !call.Argument(position).IsFunction() { return errors.New(fmt.Sprintf("expected parameter %d to be of type function", position)) } return nil } -var TypeNumber = func(context *duktape.Context, position int) error { - if !context.IsNumber(position) { +var TypeNumber = func(call otto.FunctionCall, position int) error { + if !call.Argument(position).IsNumber() { return errors.New(fmt.Sprintf("expected parameter %d to be of type number", position)) } return nil } -var TypeString = func(context *duktape.Context, position int) error { - if !context.IsString(position) { +var TypeString = func(call otto.FunctionCall, position int) error { + if !call.Argument(position).IsString() { return errors.New(fmt.Sprintf("expected parameter %d to be of type string", position)) } return nil } -var TypeObject = func(context *duktape.Context, position int) error { - if !context.IsObject(position) { +var TypeObject = func(call otto.FunctionCall, position int) error { + if !call.Argument(position).IsObject() { return errors.New(fmt.Sprintf("expected parameter %d to be of type object", position)) } return nil @@ -51,7 +51,7 @@ func (v *CallValidator) Set(index int, validator *Validator) { v.rules[index] = validator } -func (v *CallValidator) Validate(vm *duktape.Context) error { +func (v *CallValidator) Validate(vm *otto.Otto, call otto.FunctionCall) *otto.Value { v.lock.Lock() defer v.lock.Unlock() @@ -66,14 +66,14 @@ func (v *CallValidator) Validate(vm *duktape.Context) error { validator, exist := v.rules[k] if !exist { - ve := fmt.Errorf("ValidationError: couldn't find validator for type: %d", k) - return ve + ve := vm.MakeCustomError("ValidationError", fmt.Sprintf("couldn't find validator for type: %d", k)) + return &ve } v := *validator - if err := v(vm, k); err != nil { - ve := fmt.Errorf("ValidationError: %s", err.Error()) - return ve + if err := v(call, k); err != nil { + ve := vm.MakeCustomError("ValidationError", err.Error()) + return &ve } } diff --git a/dapp/validator/object.go b/dapp/validator/object.go index 5b21c1e..5969266 100644 --- a/dapp/validator/object.go +++ b/dapp/validator/object.go @@ -6,42 +6,42 @@ import ( "sync" eth "github.com/ethereum/go-ethereum/common" - duktape "gopkg.in/olebedev/go-duktape.v3" + otto "github.com/robertkrimen/otto" ) -type ObjValueValidator func(context *duktape.Context, position int) error +type ObjValueValidator func(value otto.Value, objKey string) error // validate an address -var ObjTypeAddress = func(context *duktape.Context, position int) error { - err := errors.New(fmt.Sprintf("Expected %s to be an ethereum address", context.ToString(position))) - if !context.IsString(position) { +var ObjTypeAddress = func(value otto.Value, objKey string) error { + err := errors.New(fmt.Sprintf("Expected %s to be an ethereum address", objKey)) + if !value.IsString() { return err } - if !eth.IsHexAddress(context.ToString(position)) { + if !eth.IsHexAddress(value.String()) { return err } return nil } // Validate if value is a string -var ObjTypeString = func(context *duktape.Context, position int) error { - if !context.IsString(position) { - return errors.New(fmt.Sprintf("Expected %s to be a string", context.ToString(position))) +var ObjTypeString = func(value otto.Value, objKey string) error { + if !value.IsString() { + return errors.New(fmt.Sprintf("Expected %s to be a string", objKey)) } return nil } // validate if value is an object -var ObjTypeObject = func(context *duktape.Context, position int) error { - if !context.IsObject(position) { - return fmt.Errorf("expected %s to be a object", context.ToString(position)) +var ObjTypeObject = func(value otto.Value, objKey string) error { + if !value.IsObject() { + return fmt.Errorf("expected %s to be a object", objKey) } return nil } -var ObjTypeBool = func(context *duktape.Context, position int) error { - if !context.IsBoolean(position) { - return fmt.Errorf("expected %s to be a bool", context.ToString(position)) +var ObjTypeBool = func(value otto.Value, objKey string) error { + if !value.IsBoolean() { + return fmt.Errorf("expected %s to be a bool", objKey) } return nil } @@ -76,20 +76,27 @@ func (v *ObjValidator) Set(key string, validator ObjValueValidator, required boo } -func (v *ObjValidator) Validate(context *duktape.Context, position int) error { +func (v *ObjValidator) Validate(vm *otto.Otto, obj otto.Object) *otto.Value { v.lock.Lock() defer v.lock.Unlock() for objKey, rule := range v.rules { + keyExist := false - if context.HasPropString(position, objKey) { - keyExist = true + for _, k := range obj.Keys() { + if k == objKey { + keyExist = true + } } + // exit when key is required but missing if !keyExist && rule.required { - e := fmt.Errorf("ValidationError: Missing value for required key: %s", objKey) - return e + e := vm.MakeCustomError( + "ValidationError", + fmt.Sprintf("Missing value for required key: %s", objKey), + ) + return &e } // in the case the ke doesn't exist we should just move on @@ -98,15 +105,22 @@ func (v *ObjValidator) Validate(context *duktape.Context, position int) error { } // get value for key - if !context.GetPropString(position, objKey) { - e := fmt.Errorf("InternalError: Key doesn't exist %s", objKey) - return e + value, err := obj.Get(objKey) + if err != nil { + e := vm.MakeCustomError( + "InternalError", + err.Error(), + ) + return &e } + // validate value - // @TODO find out why -1 works here and "position" doesnt - if err := rule.valueType(context, -1); err != nil { - //e := fmt.Errorf("ValidationError : Missing value for required key: %s", context.ToString(position)) - return err + if err := rule.valueType(value, objKey); err != nil { + e := vm.MakeCustomError( + "ValidationError", + err.Error(), + ) + return &e } } diff --git a/dapp/validator/object_test.go b/dapp/validator/object_test.go index 0b7c856..826fd68 100644 --- a/dapp/validator/object_test.go +++ b/dapp/validator/object_test.go @@ -3,8 +3,8 @@ package validator import ( "testing" + otto "github.com/robertkrimen/otto" require "github.com/stretchr/testify/require" - duktape "gopkg.in/olebedev/go-duktape.v3" ) func TestNewObjValidatorOptionalFields(t *testing.T) { @@ -12,12 +12,12 @@ func TestNewObjValidatorOptionalFields(t *testing.T) { v := NewObjValidator() v.Set("to", ObjTypeAddress, false) - vm := duktape.New() - err := vm.PevalString(`({})`) + vm := otto.New() + ob, err := vm.Object(`({})`) require.Nil(t, err) // expect no error since it's ok omit the address - validationErr := v.Validate(vm, -1) + validationErr := v.Validate(vm, *ob) require.Nil(t, validationErr) } @@ -30,11 +30,12 @@ func TestNewObjValidationRequiredFields(t *testing.T) { v := NewObjValidator() v.Set("to", ObjTypeAddress, true) - vm := duktape.New() - err := vm.PevalString(`({"to": "0x0b078896a3d9166da5c37ae52a5809aca48630d4"})`) + vm := otto.New() + ob, err := vm.Object(`({"to": "0x0b078896a3d9166da5c37ae52a5809aca48630d4"})`) require.Nil(t, err) + // expect no error since we provide the address - require.Nil(t, v.Validate(vm, -1)) + require.Nil(t, v.Validate(vm, *ob)) /** * Test required field - with error @@ -42,10 +43,11 @@ func TestNewObjValidationRequiredFields(t *testing.T) { v = NewObjValidator() v.Set("to", ObjTypeAddress, false) - vm = duktape.New() - err = vm.PevalString(`({})`) + vm = otto.New() + ob, err = vm.Object(`({})`) require.Nil(t, err) + // expect error since we don't pass the required address in - require.Nil(t, v.Validate(vm, -1)) + require.Nil(t, v.Validate(vm, *ob)) } diff --git a/db/bolt_to_storm.go b/db/bolt_to_storm.go index 31c695b..fbbbbf5 100644 --- a/db/bolt_to_storm.go +++ b/db/bolt_to_storm.go @@ -11,8 +11,8 @@ import ( queue "github.com/Bit-Nation/panthalassa/queue" x3dh "github.com/Bit-Nation/x3dh" storm "github.com/asdine/storm" - bolt "github.com/coreos/bbolt" dr "github.com/tiabc/doubleratchet" + bolt "go.etcd.io/bbolt" ) type BoltToStormMigration struct { @@ -289,11 +289,12 @@ func (m *BoltToStormMigration) Migrate(db *storm.DB) error { return chats.ForEach(func(partner, _ []byte) error { - if err := chatDB.CreateChat(partner); err != nil { + _, err := chatDB.CreateChat(partner) + if err != nil { return err } - chat, err := chatDB.GetChat(partner) + chat, err := chatDB.GetChatByPartner(partner) if err != nil { return err } diff --git a/db/chat_storage.go b/db/chat_storage.go index bf665b8..8b4ccda 100644 --- a/db/chat_storage.go +++ b/db/chat_storage.go @@ -1,13 +1,17 @@ package db import ( + "crypto/rand" + "encoding/hex" "errors" "fmt" + "time" aes "github.com/Bit-Nation/panthalassa/crypto/aes" km "github.com/Bit-Nation/panthalassa/keyManager" storm "github.com/asdine/storm" sq "github.com/asdine/storm/q" + uuid "github.com/satori/go.uuid" ed25519 "golang.org/x/crypto/ed25519" ) @@ -34,7 +38,6 @@ var statuses = map[Status]bool{ // validate a given message var ValidMessage = func(m Message) error { - // validate id if m.ID == "" { return errors.New("invalid message id (empty string)") } @@ -50,7 +53,7 @@ var ValidMessage = func(m Message) error { } // validate "type" of message - if m.DApp == nil && len(m.Message) == 0 { + if m.DApp == nil && m.AddUserToChat == nil && len(m.Message) == 0 { return errors.New("got invalid message - dapp and message are both nil") } @@ -87,6 +90,12 @@ type DAppMessage struct { ShouldSend bool } +type AddUserToChat struct { + Users []ed25519.PublicKey + ChatName string + ChatID []byte +} + type Message struct { DBID int `storm:"id,increment"` ID string @@ -100,20 +109,64 @@ type Message struct { Sender []byte `storm:"index"` // the UniqueID is a unix nano timestamp // it's only unique in the relation with a chat id - UniqueMsgID int64 `storm:"index"` - ChatID int `storm:"index"` + UniqueMsgID int64 `storm:"index"` + ChatID int `storm:"index"` + AddUserToChat *AddUserToChat + GroupChatID []byte } type Chat struct { - ID int `storm:"id,increment"` + ID int `storm:"id,increment"` + GroupChatName string // partner will only be filled if this is a private chat Partner ed25519.PublicKey `storm:"index,unique"` + Partners []ed25519.PublicKey UnreadMessages bool + GroupChatRemoteID []byte `storm:"unique"` db storm.Node km *km.KeyManager postPersistListener []func(event MessagePersistedEvent) } +// @todo maybe more checks +func (c *Chat) IsGroupChat() bool { + return len(c.GroupChatRemoteID) == 200 +} + +var nowAsUnix = func() int64 { + return time.Now().UnixNano() +} + +// save a raw message +func (c *Chat) SaveMessage(rawMessage []byte) error { + + id, err := uuid.NewV4() + if err != nil { + return err + } + + senderStr, err := c.km.IdentityPublicKey() + if err != nil { + return err + } + sender, err := hex.DecodeString(senderStr) + if err != nil { + return err + } + + msg := Message{ + ID: id.String(), + Message: rawMessage, + CreatedAt: nowAsUnix(), + Version: 1, + Status: StatusPersisted, + Sender: sender, + } + + // fetch chat + return c.PersistMessage(msg) +} + func (c *Chat) GetMessage(msgID int64) (*Message, error) { q := c.db.Select(sq.And( sq.Eq("ChatID", c.ID), @@ -145,7 +198,7 @@ func (c *Chat) GetMessage(msgID int64) (*Message, error) { } -// Persist Message +// Persist Message struct func (c *Chat) PersistMessage(msg Message) error { msg.ChatID = c.ID ct, err := c.km.AESEncrypt(msg.Message) @@ -232,14 +285,19 @@ func (c *Chat) Messages(start int64, amount uint) ([]*Message, error) { } type ChatStorage interface { - GetChat(partner ed25519.PublicKey) (*Chat, error) - CreateChat(partner ed25519.PublicKey) error + GetChatByPartner(pubKey ed25519.PublicKey) (*Chat, error) + GetChat(chatID int) (*Chat, error) + GetGroupChatByRemoteID(id []byte) (*Chat, error) + // returned int is the chat ID + CreateChat(partner ed25519.PublicKey) (int, error) + CreateGroupChat(partners []ed25519.PublicKey, name string) (int, error) + CreateGroupChatFromMsg(createMessage Message) error AddListener(func(e MessagePersistedEvent)) AllChats() ([]Chat, error) // set state of chat to unread messages UnreadMessages(c Chat) error // set state of chat all messages read - ReadMessages(partner ed25519.PublicKey) error + ReadMessages(chatID int) error } type MessagePersistedEvent struct { @@ -253,7 +311,74 @@ type BoltChatStorage struct { km *km.KeyManager } -func (s *BoltChatStorage) GetChat(partner ed25519.PublicKey) (*Chat, error) { +func (s *BoltChatStorage) CreateGroupChatFromMsg(msg Message) error { + + if msg.AddUserToChat == nil { + return errors.New("can't create a chat from a message without the information needed to create it") + } + + msg.AddUserToChat.Users = append(msg.AddUserToChat.Users, msg.Sender) + + c := Chat{ + GroupChatName: msg.AddUserToChat.ChatName, + Partners: msg.AddUserToChat.Users, + GroupChatRemoteID: msg.AddUserToChat.ChatID, + } + + return s.db.Save(&c) + +} + +func (s *BoltChatStorage) GetGroupChatByRemoteID(id []byte) (*Chat, error) { + + // make sure chats exist + q := s.db.Select(sq.Eq("GroupChatRemoteID", id)) + amount, err := q.Count(&Chat{}) + if err != nil { + return nil, err + } + if amount != 1 { + return nil, nil + } + + c := &Chat{ + km: s.km, + db: s.db, + postPersistListener: s.postPersistListener, + } + + if err := s.db.One("GroupChatRemoteID", id, c); err != nil { + return nil, err + } + + return c, nil + +} + +func (s *BoltChatStorage) GetChat(chatID int) (*Chat, error) { + + amountChats, err := s.db.Count(&Chat{}) + if err != nil { + return nil, err + } + if amountChats == 0 { + return nil, nil + } + + chat := &Chat{} + chat.km = s.km + chat.db = s.db + chat.postPersistListener = s.postPersistListener + + if err := s.db.One("ID", chatID, chat); err != nil { + return nil, err + } + + return chat, nil + +} + +func (s *BoltChatStorage) GetChatByPartner(partner ed25519.PublicKey) (*Chat, error) { // check if partner chat exist q := s.db.Select(sq.Eq("Partner", partner)) @@ -276,10 +401,17 @@ func (s *BoltChatStorage) GetChat(partner ed25519.PublicKey) (*Chat, error) { return &c, nil } -func (s *BoltChatStorage) CreateChat(partner ed25519.PublicKey) error { - return s.db.Save(&Chat{ +func (s *BoltChatStorage) CreateChat(partner ed25519.PublicKey) (int, error) { + + c := &Chat{ Partner: partner, - }) + } + + if err := s.db.Save(c); err != nil { + return 0, err + } + + return c.ID, nil } func (s *BoltChatStorage) UnreadMessages(c Chat) error { @@ -291,9 +423,9 @@ func (s *BoltChatStorage) UnreadMessages(c Chat) error { return s.db.Save(&fc) } -func (s *BoltChatStorage) ReadMessages(partner ed25519.PublicKey) error { +func (s *BoltChatStorage) ReadMessages(chatID int) error { var fc Chat - if err := s.db.One("Partner", partner, &fc); err != nil { + if err := s.db.One("ID", chatID, &fc); err != nil { return err } fc.UnreadMessages = false @@ -309,6 +441,52 @@ func (s *BoltChatStorage) AllChats() ([]Chat, error) { return *chats, s.db.All(chats) } +func (s *BoltChatStorage) CreateGroupChat(partners []ed25519.PublicKey, name string) (int, error) { + + // remote id + ri := make([]byte, 200) + if _, err := rand.Read(ri); err != nil { + return 0, err + } + + // group chat shared secret id + ssID := make([]byte, 200) + if _, err := rand.Read(ssID); err != nil { + return 0, err + } + + c := &Chat{ + GroupChatName: name, + Partners: partners, + GroupChatRemoteID: ri, + } + + if err := s.db.Save(c); err != nil { + return 0, err + } + + return c.ID, nil + +} + +func (c *Chat) AddChatPartners(partners []ed25519.PublicKey) error { + + for _, newPartner := range partners { + exist := false + for _, existingPartner := range c.Partners { + if hex.EncodeToString(newPartner) == hex.EncodeToString(existingPartner) { + exist = true + } + } + if exist { + c.Partners = append(c.Partners, newPartner) + } + } + + return c.db.Save(&c) + +} + func NewChatStorage(db storm.Node, listeners []func(event MessagePersistedEvent), km *km.KeyManager) *BoltChatStorage { return &BoltChatStorage{ db: db, diff --git a/db/chat_storage_test.go b/db/chat_storage_test.go index e3a1366..a0bcb60 100644 --- a/db/chat_storage_test.go +++ b/db/chat_storage_test.go @@ -97,8 +97,9 @@ func TestChatMessages(t *testing.T) { chatStor := NewChatStorage(storm, []func(e MessagePersistedEvent){}, km) // create chat - require.Nil(t, chatStor.CreateChat(partner)) - chat, err := chatStor.GetChat(partner) + _, err = chatStor.CreateChat(partner) + require.Nil(t, err) + chat, err := chatStor.GetChatByPartner(partner) require.Nil(t, err) require.NotNil(t, chat) diff --git a/db/migration.go b/db/migration.go index 935a4f9..364f367 100644 --- a/db/migration.go +++ b/db/migration.go @@ -13,7 +13,7 @@ import ( "time" storm "github.com/asdine/storm" - bolt "github.com/coreos/bbolt" + bolt "go.etcd.io/bbolt" ) type Migration interface { diff --git a/db/migration_test.go b/db/migration_test.go index 47685b0..f6d9228 100644 --- a/db/migration_test.go +++ b/db/migration_test.go @@ -11,8 +11,8 @@ import ( "time" storm "github.com/asdine/storm" - bolt "github.com/coreos/bbolt" require "github.com/stretchr/testify/require" + bolt "go.etcd.io/bbolt" ) type migration struct { diff --git a/db/test_utils.go b/db/test_utils.go index d1ba7f6..d2ae9a3 100644 --- a/db/test_utils.go +++ b/db/test_utils.go @@ -1,13 +1,14 @@ package db import ( - km "github.com/Bit-Nation/panthalassa/keyManager" - ks "github.com/Bit-Nation/panthalassa/keyStore" - mnemonic "github.com/Bit-Nation/panthalassa/mnemonic" - bolt "github.com/coreos/bbolt" "os" "path/filepath" "time" + + km "github.com/Bit-Nation/panthalassa/keyManager" + ks "github.com/Bit-Nation/panthalassa/keyStore" + mnemonic "github.com/Bit-Nation/panthalassa/mnemonic" + bolt "go.etcd.io/bbolt" ) func createKeyManager() *km.KeyManager { diff --git a/documents/call.go b/documents/call.go index 392ff14..35c2bc9 100644 --- a/documents/call.go +++ b/documents/call.go @@ -4,6 +4,14 @@ import ( "encoding/base64" "errors" "time" + + "fmt" + keyManager "github.com/Bit-Nation/panthalassa/keyManager" + bind "github.com/ethereum/go-ethereum/accounts/abi/bind" + common "github.com/ethereum/go-ethereum/common" + cid "github.com/ipfs/go-cid" + mh "github.com/multiformats/go-multihash" + "math/big" ) type DocumentCreateCall struct { @@ -97,6 +105,9 @@ func (c *DocumentAllCall) Handle(data map[string]interface{}) (map[string]interf "mime_type": d.MimeType, "description": d.Description, "title": d.Title, + "hash": d.CID, + "signature": d.Signature, + "tx_hash": d.TransactionHash, }) } return map[string]interface{}{ @@ -183,3 +194,96 @@ func (d *DocumentDeleteCall) Handle(data map[string]interface{}) (map[string]int return map[string]interface{}{}, d.s.db.DeleteStruct(&doc) } + +type DocumentSubmitCall struct { + s *Storage + km *keyManager.KeyManager + n *NotaryMulti +} + +func NewDocumentNotariseCall(s *Storage, km *keyManager.KeyManager, n *NotaryMulti) *DocumentSubmitCall { + return &DocumentSubmitCall{ + s: s, + km: km, + n: n, + } +} + +func (d *DocumentSubmitCall) CallID() string { + return "DOCUMENT:NOTARISE" +} + +func (d *DocumentSubmitCall) Validate(map[string]interface{}) error { + return nil +} + +func (d *DocumentSubmitCall) Handle(data map[string]interface{}) (map[string]interface{}, error) { + + docID, k := data["doc_id"].(float64) + if !k { + return map[string]interface{}{}, errors.New("expect doc_id to be an integer") + } + + var doc Document + if err := d.s.db.One("ID", int(docID), &doc); err != nil { + return map[string]interface{}{}, err + } + + // decrypt document content + docContent, err := d.km.AESDecrypt(doc.EncryptedContent) + if err != nil { + return map[string]interface{}{}, err + } + + // assign plain document content + doc.Content = docContent + + // hash document + docHash, err := mh.Sum(doc.Content, mh.SHA2_256, -1) + if err != nil { + return map[string]interface{}{}, err + } + + docContentCID := cid.NewCidV1(cid.Raw, docHash).Bytes() + + // attach cid to document + doc.CID = docContentCID + + // sign cid + cidSignature, err := d.km.IdentitySign(docContentCID) + if err != nil { + return map[string]interface{}{}, err + } + + // attach signature to doc + doc.Signature = cidSignature + + ethAddr, err := d.km.GetEthereumAddress() + if err != nil { + return map[string]interface{}{}, err + } + + // fetch the notary fee + notaryFee, err := d.n.NotaryFee(nil) + if err != nil { + return nil, err + } + fmt.Println(docContentCID) + fmt.Println(cidSignature) + // submit tx to chain + tx, err := d.n.NotarizeTwo(&bind.TransactOpts{ + From: common.HexToAddress(ethAddr), + Signer: d.km.SignEthTx, + Value: notaryFee, + GasLimit: 500000, + GasPrice: big.NewInt(50000000000), + }, docContentCID, cidSignature) + if err != nil { + return map[string]interface{}{}, err + } + + doc.TransactionHash = tx.Hash().Hex() + + return map[string]interface{}{}, d.s.Save(&doc) + +} diff --git a/documents/notary_contract.go b/documents/notary_contract.go new file mode 100644 index 0000000..6b191e1 --- /dev/null +++ b/documents/notary_contract.go @@ -0,0 +1,1388 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package documents + +import ( + "math/big" + "strings" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" +) + +// NotaryABI is the input ABI used to generate the binding from. +const NotaryABI = "[{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"records\",\"outputs\":[{\"name\":\"notarisedData\",\"type\":\"bytes\"},{\"name\":\"timestamp\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[],\"name\":\"renounceOwnership\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_fee\",\"type\":\"uint256\"}],\"name\":\"setNotarisationFee\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"notarisationFee\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_notarisedData\",\"type\":\"bytes\"}],\"name\":\"record\",\"outputs\":[{\"name\":\"\",\"type\":\"bytes\"},{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_record\",\"type\":\"bytes\"}],\"name\":\"notarize\",\"outputs\":[],\"payable\":true,\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"name\":\"_owner\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"previousOwner\",\"type\":\"address\"}],\"name\":\"OwnershipRenounced\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"}]" + +// NotaryBin is the compiled bytecode used for deploying new contracts. +const NotaryBin = `0x608060405234801561001057600080fd5b506040516020806107e5833981016040525160008054600160a060020a03909216600160a060020a0319928316331790921691909117905561078e806100576000396000f30060806040526004361061008d5763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166301e647258114610092578063715018a6146101295780638da5cb5b14610140578063c0496e5714610171578063c9d3a88514610189578063e1112648146101b0578063f2fde38b14610209578063fb1ace341461022a575b600080fd5b34801561009e57600080fd5b506100aa600435610276565b6040518080602001838152602001828103825284818151815260200191508051906020019080838360005b838110156100ed5781810151838201526020016100d5565b50505050905090810190601f16801561011a5780820380516001836020036101000a031916815260200191505b50935050505060405180910390f35b34801561013557600080fd5b5061013e610318565b005b34801561014c57600080fd5b50610155610384565b60408051600160a060020a039092168252519081900360200190f35b34801561017d57600080fd5b5061013e600435610393565b34801561019557600080fd5b5061019e6103af565b60408051918252519081900360200190f35b3480156101bc57600080fd5b506040805160206004803580820135601f81018490048402850184019095528484526100aa9436949293602493928401919081908401838280828437509497506103b59650505050505050565b34801561021557600080fd5b5061013e600160a060020a03600435166104ea565b6040805160206004803580820135601f810184900484028501840190955284845261013e94369492936024939284019190819084018382808284375094975061050d9650505050505050565b60016020818152600092835260409283902080548451600294821615610100026000190190911693909304601f81018390048302840183019094528383529283918301828280156103085780601f106102dd57610100808354040283529160200191610308565b820191906000526020600020905b8154815290600101906020018083116102eb57829003601f168201915b5050505050908060010154905082565b600054600160a060020a0316331461032f57600080fd5b60008054604051600160a060020a03909116917ff8df31144d9c2f0f6b59d69b8b98abd5459d07f2742c4df920b25aae33c6482091a26000805473ffffffffffffffffffffffffffffffffffffffff19169055565b600054600160a060020a031681565b600054600160a060020a031633146103aa57600080fd5b600255565b60025481565b606060006103c16106af565b60016000856040518082805190602001908083835b602083106103f55780518252601f1990920191602091820191016103d6565b518151600019602094850361010090810a8201928316921993909316919091179092526040805196909401869003909520885287820198909852958101600020815181546060601f60026001841615909802909b019091169590950498890188900490970287018401825290860187815295969095879550935085929091508401828280156104c55780601f1061049a576101008083540402835291602001916104c5565b820191906000526020600020905b8154815290600101906020018083116104a857829003601f168201915b5050509183525050600191909101546020918201528151910151909590945092505050565b600054600160a060020a0316331461050157600080fd5b61050a81610632565b50565b60025460009034101561051f57600080fd5b816040518082805190602001908083835b6020831061054f5780518252601f199092019160209182019101610530565b51815160209384036101000a6000190180199092169116179052604080519290940182900390912060008181526001928390529390932001549194505015915061059a905057600080fd5b600054600160a060020a0316156105e85760008054604051600160a060020a0390911691303180156108fc02929091818181858888f193505050501580156105e6573d6000803e3d6000fd5b505b6040805180820182528381524260208083019190915260008481526001825292909220815180519293919261062092849201906106c7565b50602082015181600101559050505050565b600160a060020a038116151561064757600080fd5b60008054604051600160a060020a03808516939216917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a36000805473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a0392909216919091179055565b60408051808201909152606081526000602082015290565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061070857805160ff1916838001178555610735565b82800160010185558215610735579182015b8281111561073557825182559160200191906001019061071a565b50610741929150610745565b5090565b61075f91905b80821115610741576000815560010161074b565b905600a165627a7a7230582001b0252bf7f25e941fd126beef7b2b40c7c9922b862a8bd543807bd32f2cf4900029` + +// DeployNotary deploys a new Ethereum contract, binding an instance of Notary to it. +func DeployNotary(auth *bind.TransactOpts, backend bind.ContractBackend, _owner common.Address) (common.Address, *types.Transaction, *Notary, error) { + parsed, err := abi.JSON(strings.NewReader(NotaryABI)) + if err != nil { + return common.Address{}, nil, nil, err + } + address, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(NotaryBin), backend, _owner) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &Notary{NotaryCaller: NotaryCaller{contract: contract}, NotaryTransactor: NotaryTransactor{contract: contract}, NotaryFilterer: NotaryFilterer{contract: contract}}, nil +} + +// Notary is an auto generated Go binding around an Ethereum contract. +type Notary struct { + NotaryCaller // Read-only binding to the contract + NotaryTransactor // Write-only binding to the contract + NotaryFilterer // Log filterer for contract events +} + +// NotaryCaller is an auto generated read-only Go binding around an Ethereum contract. +type NotaryCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// NotaryTransactor is an auto generated write-only Go binding around an Ethereum contract. +type NotaryTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// NotaryFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type NotaryFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// NotarySession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type NotarySession struct { + Contract *Notary // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// NotaryCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type NotaryCallerSession struct { + Contract *NotaryCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// NotaryTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type NotaryTransactorSession struct { + Contract *NotaryTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// NotaryRaw is an auto generated low-level Go binding around an Ethereum contract. +type NotaryRaw struct { + Contract *Notary // Generic contract binding to access the raw methods on +} + +// NotaryCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type NotaryCallerRaw struct { + Contract *NotaryCaller // Generic read-only contract binding to access the raw methods on +} + +// NotaryTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type NotaryTransactorRaw struct { + Contract *NotaryTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewNotary creates a new instance of Notary, bound to a specific deployed contract. +func NewNotary(address common.Address, backend bind.ContractBackend) (*Notary, error) { + contract, err := bindNotary(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &Notary{NotaryCaller: NotaryCaller{contract: contract}, NotaryTransactor: NotaryTransactor{contract: contract}, NotaryFilterer: NotaryFilterer{contract: contract}}, nil +} + +// NewNotaryCaller creates a new read-only instance of Notary, bound to a specific deployed contract. +func NewNotaryCaller(address common.Address, caller bind.ContractCaller) (*NotaryCaller, error) { + contract, err := bindNotary(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &NotaryCaller{contract: contract}, nil +} + +// NewNotaryTransactor creates a new write-only instance of Notary, bound to a specific deployed contract. +func NewNotaryTransactor(address common.Address, transactor bind.ContractTransactor) (*NotaryTransactor, error) { + contract, err := bindNotary(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &NotaryTransactor{contract: contract}, nil +} + +// NewNotaryFilterer creates a new log filterer instance of Notary, bound to a specific deployed contract. +func NewNotaryFilterer(address common.Address, filterer bind.ContractFilterer) (*NotaryFilterer, error) { + contract, err := bindNotary(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &NotaryFilterer{contract: contract}, nil +} + +// bindNotary binds a generic wrapper to an already deployed contract. +func bindNotary(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := abi.JSON(strings.NewReader(NotaryABI)) + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_Notary *NotaryRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + return _Notary.Contract.NotaryCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_Notary *NotaryRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Notary.Contract.NotaryTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_Notary *NotaryRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _Notary.Contract.NotaryTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_Notary *NotaryCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + return _Notary.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_Notary *NotaryTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Notary.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_Notary *NotaryTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _Notary.Contract.contract.Transact(opts, method, params...) +} + +// NotarisationFee is a free data retrieval call binding the contract method 0xc9d3a885. +// +// Solidity: function notarisationFee() constant returns(uint256) +func (_Notary *NotaryCaller) NotarisationFee(opts *bind.CallOpts) (*big.Int, error) { + var ( + ret0 = new(*big.Int) + ) + out := ret0 + err := _Notary.contract.Call(opts, out, "notarisationFee") + return *ret0, err +} + +// NotarisationFee is a free data retrieval call binding the contract method 0xc9d3a885. +// +// Solidity: function notarisationFee() constant returns(uint256) +func (_Notary *NotarySession) NotarisationFee() (*big.Int, error) { + return _Notary.Contract.NotarisationFee(&_Notary.CallOpts) +} + +// NotarisationFee is a free data retrieval call binding the contract method 0xc9d3a885. +// +// Solidity: function notarisationFee() constant returns(uint256) +func (_Notary *NotaryCallerSession) NotarisationFee() (*big.Int, error) { + return _Notary.Contract.NotarisationFee(&_Notary.CallOpts) +} + +// Owner is a free data retrieval call binding the contract method 0x8da5cb5b. +// +// Solidity: function owner() constant returns(address) +func (_Notary *NotaryCaller) Owner(opts *bind.CallOpts) (common.Address, error) { + var ( + ret0 = new(common.Address) + ) + out := ret0 + err := _Notary.contract.Call(opts, out, "owner") + return *ret0, err +} + +// Owner is a free data retrieval call binding the contract method 0x8da5cb5b. +// +// Solidity: function owner() constant returns(address) +func (_Notary *NotarySession) Owner() (common.Address, error) { + return _Notary.Contract.Owner(&_Notary.CallOpts) +} + +// Owner is a free data retrieval call binding the contract method 0x8da5cb5b. +// +// Solidity: function owner() constant returns(address) +func (_Notary *NotaryCallerSession) Owner() (common.Address, error) { + return _Notary.Contract.Owner(&_Notary.CallOpts) +} + +// Record is a free data retrieval call binding the contract method 0xe1112648. +// +// Solidity: function record(_notarisedData bytes) constant returns(bytes, uint256) +func (_Notary *NotaryCaller) Record(opts *bind.CallOpts, _notarisedData []byte) ([]byte, *big.Int, error) { + var ( + ret0 = new([]byte) + ret1 = new(*big.Int) + ) + out := &[]interface{}{ + ret0, + ret1, + } + err := _Notary.contract.Call(opts, out, "record", _notarisedData) + return *ret0, *ret1, err +} + +// Record is a free data retrieval call binding the contract method 0xe1112648. +// +// Solidity: function record(_notarisedData bytes) constant returns(bytes, uint256) +func (_Notary *NotarySession) Record(_notarisedData []byte) ([]byte, *big.Int, error) { + return _Notary.Contract.Record(&_Notary.CallOpts, _notarisedData) +} + +// Record is a free data retrieval call binding the contract method 0xe1112648. +// +// Solidity: function record(_notarisedData bytes) constant returns(bytes, uint256) +func (_Notary *NotaryCallerSession) Record(_notarisedData []byte) ([]byte, *big.Int, error) { + return _Notary.Contract.Record(&_Notary.CallOpts, _notarisedData) +} + +// Records is a free data retrieval call binding the contract method 0x01e64725. +// +// Solidity: function records( bytes32) constant returns(notarisedData bytes, timestamp uint256) +func (_Notary *NotaryCaller) Records(opts *bind.CallOpts, arg0 [32]byte) (struct { + NotarisedData []byte + Timestamp *big.Int +}, error) { + ret := new(struct { + NotarisedData []byte + Timestamp *big.Int + }) + out := ret + err := _Notary.contract.Call(opts, out, "records", arg0) + return *ret, err +} + +// Records is a free data retrieval call binding the contract method 0x01e64725. +// +// Solidity: function records( bytes32) constant returns(notarisedData bytes, timestamp uint256) +func (_Notary *NotarySession) Records(arg0 [32]byte) (struct { + NotarisedData []byte + Timestamp *big.Int +}, error) { + return _Notary.Contract.Records(&_Notary.CallOpts, arg0) +} + +// Records is a free data retrieval call binding the contract method 0x01e64725. +// +// Solidity: function records( bytes32) constant returns(notarisedData bytes, timestamp uint256) +func (_Notary *NotaryCallerSession) Records(arg0 [32]byte) (struct { + NotarisedData []byte + Timestamp *big.Int +}, error) { + return _Notary.Contract.Records(&_Notary.CallOpts, arg0) +} + +// Notarize is a paid mutator transaction binding the contract method 0xfb1ace34. +// +// Solidity: function notarize(_record bytes) returns() +func (_Notary *NotaryTransactor) Notarize(opts *bind.TransactOpts, _record []byte) (*types.Transaction, error) { + return _Notary.contract.Transact(opts, "notarize", _record) +} + +// Notarize is a paid mutator transaction binding the contract method 0xfb1ace34. +// +// Solidity: function notarize(_record bytes) returns() +func (_Notary *NotarySession) Notarize(_record []byte) (*types.Transaction, error) { + return _Notary.Contract.Notarize(&_Notary.TransactOpts, _record) +} + +// Notarize is a paid mutator transaction binding the contract method 0xfb1ace34. +// +// Solidity: function notarize(_record bytes) returns() +func (_Notary *NotaryTransactorSession) Notarize(_record []byte) (*types.Transaction, error) { + return _Notary.Contract.Notarize(&_Notary.TransactOpts, _record) +} + +// RenounceOwnership is a paid mutator transaction binding the contract method 0x715018a6. +// +// Solidity: function renounceOwnership() returns() +func (_Notary *NotaryTransactor) RenounceOwnership(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Notary.contract.Transact(opts, "renounceOwnership") +} + +// RenounceOwnership is a paid mutator transaction binding the contract method 0x715018a6. +// +// Solidity: function renounceOwnership() returns() +func (_Notary *NotarySession) RenounceOwnership() (*types.Transaction, error) { + return _Notary.Contract.RenounceOwnership(&_Notary.TransactOpts) +} + +// RenounceOwnership is a paid mutator transaction binding the contract method 0x715018a6. +// +// Solidity: function renounceOwnership() returns() +func (_Notary *NotaryTransactorSession) RenounceOwnership() (*types.Transaction, error) { + return _Notary.Contract.RenounceOwnership(&_Notary.TransactOpts) +} + +// SetNotarisationFee is a paid mutator transaction binding the contract method 0xc0496e57. +// +// Solidity: function setNotarisationFee(_fee uint256) returns() +func (_Notary *NotaryTransactor) SetNotarisationFee(opts *bind.TransactOpts, _fee *big.Int) (*types.Transaction, error) { + return _Notary.contract.Transact(opts, "setNotarisationFee", _fee) +} + +// SetNotarisationFee is a paid mutator transaction binding the contract method 0xc0496e57. +// +// Solidity: function setNotarisationFee(_fee uint256) returns() +func (_Notary *NotarySession) SetNotarisationFee(_fee *big.Int) (*types.Transaction, error) { + return _Notary.Contract.SetNotarisationFee(&_Notary.TransactOpts, _fee) +} + +// SetNotarisationFee is a paid mutator transaction binding the contract method 0xc0496e57. +// +// Solidity: function setNotarisationFee(_fee uint256) returns() +func (_Notary *NotaryTransactorSession) SetNotarisationFee(_fee *big.Int) (*types.Transaction, error) { + return _Notary.Contract.SetNotarisationFee(&_Notary.TransactOpts, _fee) +} + +// TransferOwnership is a paid mutator transaction binding the contract method 0xf2fde38b. +// +// Solidity: function transferOwnership(_newOwner address) returns() +func (_Notary *NotaryTransactor) TransferOwnership(opts *bind.TransactOpts, _newOwner common.Address) (*types.Transaction, error) { + return _Notary.contract.Transact(opts, "transferOwnership", _newOwner) +} + +// TransferOwnership is a paid mutator transaction binding the contract method 0xf2fde38b. +// +// Solidity: function transferOwnership(_newOwner address) returns() +func (_Notary *NotarySession) TransferOwnership(_newOwner common.Address) (*types.Transaction, error) { + return _Notary.Contract.TransferOwnership(&_Notary.TransactOpts, _newOwner) +} + +// TransferOwnership is a paid mutator transaction binding the contract method 0xf2fde38b. +// +// Solidity: function transferOwnership(_newOwner address) returns() +func (_Notary *NotaryTransactorSession) TransferOwnership(_newOwner common.Address) (*types.Transaction, error) { + return _Notary.Contract.TransferOwnership(&_Notary.TransactOpts, _newOwner) +} + +// NotaryOwnershipRenouncedIterator is returned from FilterOwnershipRenounced and is used to iterate over the raw logs and unpacked data for OwnershipRenounced events raised by the Notary contract. +type NotaryOwnershipRenouncedIterator struct { + Event *NotaryOwnershipRenounced // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *NotaryOwnershipRenouncedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(NotaryOwnershipRenounced) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(NotaryOwnershipRenounced) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *NotaryOwnershipRenouncedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *NotaryOwnershipRenouncedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// NotaryOwnershipRenounced represents a OwnershipRenounced event raised by the Notary contract. +type NotaryOwnershipRenounced struct { + PreviousOwner common.Address + Raw types.Log // Blockchain specific contextual infos +} + +// FilterOwnershipRenounced is a free log retrieval operation binding the contract event 0xf8df31144d9c2f0f6b59d69b8b98abd5459d07f2742c4df920b25aae33c64820. +// +// Solidity: e OwnershipRenounced(previousOwner indexed address) +func (_Notary *NotaryFilterer) FilterOwnershipRenounced(opts *bind.FilterOpts, previousOwner []common.Address) (*NotaryOwnershipRenouncedIterator, error) { + + var previousOwnerRule []interface{} + for _, previousOwnerItem := range previousOwner { + previousOwnerRule = append(previousOwnerRule, previousOwnerItem) + } + + logs, sub, err := _Notary.contract.FilterLogs(opts, "OwnershipRenounced", previousOwnerRule) + if err != nil { + return nil, err + } + return &NotaryOwnershipRenouncedIterator{contract: _Notary.contract, event: "OwnershipRenounced", logs: logs, sub: sub}, nil +} + +// WatchOwnershipRenounced is a free log subscription operation binding the contract event 0xf8df31144d9c2f0f6b59d69b8b98abd5459d07f2742c4df920b25aae33c64820. +// +// Solidity: e OwnershipRenounced(previousOwner indexed address) +func (_Notary *NotaryFilterer) WatchOwnershipRenounced(opts *bind.WatchOpts, sink chan<- *NotaryOwnershipRenounced, previousOwner []common.Address) (event.Subscription, error) { + + var previousOwnerRule []interface{} + for _, previousOwnerItem := range previousOwner { + previousOwnerRule = append(previousOwnerRule, previousOwnerItem) + } + + logs, sub, err := _Notary.contract.WatchLogs(opts, "OwnershipRenounced", previousOwnerRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(NotaryOwnershipRenounced) + if err := _Notary.contract.UnpackLog(event, "OwnershipRenounced", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// NotaryOwnershipTransferredIterator is returned from FilterOwnershipTransferred and is used to iterate over the raw logs and unpacked data for OwnershipTransferred events raised by the Notary contract. +type NotaryOwnershipTransferredIterator struct { + Event *NotaryOwnershipTransferred // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *NotaryOwnershipTransferredIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(NotaryOwnershipTransferred) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(NotaryOwnershipTransferred) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *NotaryOwnershipTransferredIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *NotaryOwnershipTransferredIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// NotaryOwnershipTransferred represents a OwnershipTransferred event raised by the Notary contract. +type NotaryOwnershipTransferred struct { + PreviousOwner common.Address + NewOwner common.Address + Raw types.Log // Blockchain specific contextual infos +} + +// FilterOwnershipTransferred is a free log retrieval operation binding the contract event 0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0. +// +// Solidity: e OwnershipTransferred(previousOwner indexed address, newOwner indexed address) +func (_Notary *NotaryFilterer) FilterOwnershipTransferred(opts *bind.FilterOpts, previousOwner []common.Address, newOwner []common.Address) (*NotaryOwnershipTransferredIterator, error) { + + var previousOwnerRule []interface{} + for _, previousOwnerItem := range previousOwner { + previousOwnerRule = append(previousOwnerRule, previousOwnerItem) + } + var newOwnerRule []interface{} + for _, newOwnerItem := range newOwner { + newOwnerRule = append(newOwnerRule, newOwnerItem) + } + + logs, sub, err := _Notary.contract.FilterLogs(opts, "OwnershipTransferred", previousOwnerRule, newOwnerRule) + if err != nil { + return nil, err + } + return &NotaryOwnershipTransferredIterator{contract: _Notary.contract, event: "OwnershipTransferred", logs: logs, sub: sub}, nil +} + +// WatchOwnershipTransferred is a free log subscription operation binding the contract event 0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0. +// +// Solidity: e OwnershipTransferred(previousOwner indexed address, newOwner indexed address) +func (_Notary *NotaryFilterer) WatchOwnershipTransferred(opts *bind.WatchOpts, sink chan<- *NotaryOwnershipTransferred, previousOwner []common.Address, newOwner []common.Address) (event.Subscription, error) { + + var previousOwnerRule []interface{} + for _, previousOwnerItem := range previousOwner { + previousOwnerRule = append(previousOwnerRule, previousOwnerItem) + } + var newOwnerRule []interface{} + for _, newOwnerItem := range newOwner { + newOwnerRule = append(newOwnerRule, newOwnerItem) + } + + logs, sub, err := _Notary.contract.WatchLogs(opts, "OwnershipTransferred", previousOwnerRule, newOwnerRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(NotaryOwnershipTransferred) + if err := _Notary.contract.UnpackLog(event, "OwnershipTransferred", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// NotaryMultiABI is the input ABI used to generate the binding from. +const NotaryMultiABI = "[{\"constant\":false,\"inputs\":[{\"name\":\"_firstRecord\",\"type\":\"bytes\"},{\"name\":\"_secondRecord\",\"type\":\"bytes\"}],\"name\":\"notarizeTwo\",\"outputs\":[],\"payable\":true,\"stateMutability\":\"payable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"notaryFee\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"notary\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"name\":\"_notary\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"}]" + +// NotaryMultiBin is the compiled bytecode used for deploying new contracts. +const NotaryMultiBin = `0x608060405234801561001057600080fd5b5060405160208061047d833981016040525160008054600160a060020a03909216600160a060020a031990921691909117905561042b806100526000396000f3006080604052600436106100565763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166309267785811461005b578063835c853b146100e75780639d54c79d1461010e575b600080fd5b6040805160206004803580820135601f81018490048402850184019095528484526100e594369492936024939284019190819084018382808284375050604080516020601f89358b018035918201839004830284018301909452808352979a99988101979196509182019450925082915084018382808284375094975061014c9650505050505050565b005b3480156100f357600080fd5b506100fc610328565b60408051918252519081900360200190f35b34801561011a57600080fd5b506101236103e3565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252519081900360200190f35b600080546040517ffb1ace3400000000000000000000000000000000000000000000000000000000815260206004820181815286516024840152865173ffffffffffffffffffffffffffffffffffffffff9094169463fb1ace34948894929384936044019290860191908190849084905b838110156101d55781810151838201526020016101bd565b50505050905090810190601f1680156102025780820380516001836020036101000a031916815260200191505b5092505050600060405180830381600087803b15801561022157600080fd5b505af1158015610235573d6000803e3d6000fd5b5050600080546040517ffb1ace3400000000000000000000000000000000000000000000000000000000815260206004820181815287516024840152875173ffffffffffffffffffffffffffffffffffffffff909416965063fb1ace349550879490938493604401928601918190849084905b838110156102c05781810151838201526020016102a8565b50505050905090810190601f1680156102ed5780820380516001836020036101000a031916815260200191505b5092505050600060405180830381600087803b15801561030c57600080fd5b505af1158015610320573d6000803e3d6000fd5b505050505050565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663c9d3a8856040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401602060405180830381600087803b1580156103af57600080fd5b505af11580156103c3573d6000803e3d6000fd5b505050506040513d60208110156103d957600080fd5b5051600202905090565b60005473ffffffffffffffffffffffffffffffffffffffff16815600a165627a7a7230582069c7b45b3c6de0d2511a2b2d5d56868ed00f9c9ba5582d5907c57c67f90b4f620029` + +// DeployNotaryMulti deploys a new Ethereum contract, binding an instance of NotaryMulti to it. +func DeployNotaryMulti(auth *bind.TransactOpts, backend bind.ContractBackend, _notary common.Address) (common.Address, *types.Transaction, *NotaryMulti, error) { + parsed, err := abi.JSON(strings.NewReader(NotaryMultiABI)) + if err != nil { + return common.Address{}, nil, nil, err + } + address, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(NotaryMultiBin), backend, _notary) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &NotaryMulti{NotaryMultiCaller: NotaryMultiCaller{contract: contract}, NotaryMultiTransactor: NotaryMultiTransactor{contract: contract}, NotaryMultiFilterer: NotaryMultiFilterer{contract: contract}}, nil +} + +// NotaryMulti is an auto generated Go binding around an Ethereum contract. +type NotaryMulti struct { + NotaryMultiCaller // Read-only binding to the contract + NotaryMultiTransactor // Write-only binding to the contract + NotaryMultiFilterer // Log filterer for contract events +} + +// NotaryMultiCaller is an auto generated read-only Go binding around an Ethereum contract. +type NotaryMultiCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// NotaryMultiTransactor is an auto generated write-only Go binding around an Ethereum contract. +type NotaryMultiTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// NotaryMultiFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type NotaryMultiFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// NotaryMultiSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type NotaryMultiSession struct { + Contract *NotaryMulti // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// NotaryMultiCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type NotaryMultiCallerSession struct { + Contract *NotaryMultiCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// NotaryMultiTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type NotaryMultiTransactorSession struct { + Contract *NotaryMultiTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// NotaryMultiRaw is an auto generated low-level Go binding around an Ethereum contract. +type NotaryMultiRaw struct { + Contract *NotaryMulti // Generic contract binding to access the raw methods on +} + +// NotaryMultiCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type NotaryMultiCallerRaw struct { + Contract *NotaryMultiCaller // Generic read-only contract binding to access the raw methods on +} + +// NotaryMultiTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type NotaryMultiTransactorRaw struct { + Contract *NotaryMultiTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewNotaryMulti creates a new instance of NotaryMulti, bound to a specific deployed contract. +func NewNotaryMulti(address common.Address, backend bind.ContractBackend) (*NotaryMulti, error) { + contract, err := bindNotaryMulti(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &NotaryMulti{NotaryMultiCaller: NotaryMultiCaller{contract: contract}, NotaryMultiTransactor: NotaryMultiTransactor{contract: contract}, NotaryMultiFilterer: NotaryMultiFilterer{contract: contract}}, nil +} + +// NewNotaryMultiCaller creates a new read-only instance of NotaryMulti, bound to a specific deployed contract. +func NewNotaryMultiCaller(address common.Address, caller bind.ContractCaller) (*NotaryMultiCaller, error) { + contract, err := bindNotaryMulti(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &NotaryMultiCaller{contract: contract}, nil +} + +// NewNotaryMultiTransactor creates a new write-only instance of NotaryMulti, bound to a specific deployed contract. +func NewNotaryMultiTransactor(address common.Address, transactor bind.ContractTransactor) (*NotaryMultiTransactor, error) { + contract, err := bindNotaryMulti(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &NotaryMultiTransactor{contract: contract}, nil +} + +// NewNotaryMultiFilterer creates a new log filterer instance of NotaryMulti, bound to a specific deployed contract. +func NewNotaryMultiFilterer(address common.Address, filterer bind.ContractFilterer) (*NotaryMultiFilterer, error) { + contract, err := bindNotaryMulti(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &NotaryMultiFilterer{contract: contract}, nil +} + +// bindNotaryMulti binds a generic wrapper to an already deployed contract. +func bindNotaryMulti(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := abi.JSON(strings.NewReader(NotaryMultiABI)) + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_NotaryMulti *NotaryMultiRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + return _NotaryMulti.Contract.NotaryMultiCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_NotaryMulti *NotaryMultiRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _NotaryMulti.Contract.NotaryMultiTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_NotaryMulti *NotaryMultiRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _NotaryMulti.Contract.NotaryMultiTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_NotaryMulti *NotaryMultiCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + return _NotaryMulti.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_NotaryMulti *NotaryMultiTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _NotaryMulti.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_NotaryMulti *NotaryMultiTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _NotaryMulti.Contract.contract.Transact(opts, method, params...) +} + +// Notary is a free data retrieval call binding the contract method 0x9d54c79d. +// +// Solidity: function notary() constant returns(address) +func (_NotaryMulti *NotaryMultiCaller) Notary(opts *bind.CallOpts) (common.Address, error) { + var ( + ret0 = new(common.Address) + ) + out := ret0 + err := _NotaryMulti.contract.Call(opts, out, "notary") + return *ret0, err +} + +// Notary is a free data retrieval call binding the contract method 0x9d54c79d. +// +// Solidity: function notary() constant returns(address) +func (_NotaryMulti *NotaryMultiSession) Notary() (common.Address, error) { + return _NotaryMulti.Contract.Notary(&_NotaryMulti.CallOpts) +} + +// Notary is a free data retrieval call binding the contract method 0x9d54c79d. +// +// Solidity: function notary() constant returns(address) +func (_NotaryMulti *NotaryMultiCallerSession) Notary() (common.Address, error) { + return _NotaryMulti.Contract.Notary(&_NotaryMulti.CallOpts) +} + +// NotaryFee is a free data retrieval call binding the contract method 0x835c853b. +// +// Solidity: function notaryFee() constant returns(uint256) +func (_NotaryMulti *NotaryMultiCaller) NotaryFee(opts *bind.CallOpts) (*big.Int, error) { + var ( + ret0 = new(*big.Int) + ) + out := ret0 + err := _NotaryMulti.contract.Call(opts, out, "notaryFee") + return *ret0, err +} + +// NotaryFee is a free data retrieval call binding the contract method 0x835c853b. +// +// Solidity: function notaryFee() constant returns(uint256) +func (_NotaryMulti *NotaryMultiSession) NotaryFee() (*big.Int, error) { + return _NotaryMulti.Contract.NotaryFee(&_NotaryMulti.CallOpts) +} + +// NotaryFee is a free data retrieval call binding the contract method 0x835c853b. +// +// Solidity: function notaryFee() constant returns(uint256) +func (_NotaryMulti *NotaryMultiCallerSession) NotaryFee() (*big.Int, error) { + return _NotaryMulti.Contract.NotaryFee(&_NotaryMulti.CallOpts) +} + +// NotarizeTwo is a paid mutator transaction binding the contract method 0x09267785. +// +// Solidity: function notarizeTwo(_firstRecord bytes, _secondRecord bytes) returns() +func (_NotaryMulti *NotaryMultiTransactor) NotarizeTwo(opts *bind.TransactOpts, _firstRecord []byte, _secondRecord []byte) (*types.Transaction, error) { + return _NotaryMulti.contract.Transact(opts, "notarizeTwo", _firstRecord, _secondRecord) +} + +// NotarizeTwo is a paid mutator transaction binding the contract method 0x09267785. +// +// Solidity: function notarizeTwo(_firstRecord bytes, _secondRecord bytes) returns() +func (_NotaryMulti *NotaryMultiSession) NotarizeTwo(_firstRecord []byte, _secondRecord []byte) (*types.Transaction, error) { + return _NotaryMulti.Contract.NotarizeTwo(&_NotaryMulti.TransactOpts, _firstRecord, _secondRecord) +} + +// NotarizeTwo is a paid mutator transaction binding the contract method 0x09267785. +// +// Solidity: function notarizeTwo(_firstRecord bytes, _secondRecord bytes) returns() +func (_NotaryMulti *NotaryMultiTransactorSession) NotarizeTwo(_firstRecord []byte, _secondRecord []byte) (*types.Transaction, error) { + return _NotaryMulti.Contract.NotarizeTwo(&_NotaryMulti.TransactOpts, _firstRecord, _secondRecord) +} + +// OwnableABI is the input ABI used to generate the binding from. +const OwnableABI = "[{\"constant\":false,\"inputs\":[],\"name\":\"renounceOwnership\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"previousOwner\",\"type\":\"address\"}],\"name\":\"OwnershipRenounced\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"}]" + +// OwnableBin is the compiled bytecode used for deploying new contracts. +const OwnableBin = `0x608060405234801561001057600080fd5b5060008054600160a060020a0319163317905561020b806100326000396000f3006080604052600436106100565763ffffffff7c0100000000000000000000000000000000000000000000000000000000600035041663715018a6811461005b5780638da5cb5b14610072578063f2fde38b146100a3575b600080fd5b34801561006757600080fd5b506100706100c4565b005b34801561007e57600080fd5b50610087610130565b60408051600160a060020a039092168252519081900360200190f35b3480156100af57600080fd5b50610070600160a060020a036004351661013f565b600054600160a060020a031633146100db57600080fd5b60008054604051600160a060020a03909116917ff8df31144d9c2f0f6b59d69b8b98abd5459d07f2742c4df920b25aae33c6482091a26000805473ffffffffffffffffffffffffffffffffffffffff19169055565b600054600160a060020a031681565b600054600160a060020a0316331461015657600080fd5b61015f81610162565b50565b600160a060020a038116151561017757600080fd5b60008054604051600160a060020a03808516939216917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a36000805473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a03929092169190911790555600a165627a7a72305820939090677929d52a8b7fcd09005569e1345b68565d3dee73b40c555b2f7d4d300029` + +// DeployOwnable deploys a new Ethereum contract, binding an instance of Ownable to it. +func DeployOwnable(auth *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, *Ownable, error) { + parsed, err := abi.JSON(strings.NewReader(OwnableABI)) + if err != nil { + return common.Address{}, nil, nil, err + } + address, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(OwnableBin), backend) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &Ownable{OwnableCaller: OwnableCaller{contract: contract}, OwnableTransactor: OwnableTransactor{contract: contract}, OwnableFilterer: OwnableFilterer{contract: contract}}, nil +} + +// Ownable is an auto generated Go binding around an Ethereum contract. +type Ownable struct { + OwnableCaller // Read-only binding to the contract + OwnableTransactor // Write-only binding to the contract + OwnableFilterer // Log filterer for contract events +} + +// OwnableCaller is an auto generated read-only Go binding around an Ethereum contract. +type OwnableCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// OwnableTransactor is an auto generated write-only Go binding around an Ethereum contract. +type OwnableTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// OwnableFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type OwnableFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// OwnableSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type OwnableSession struct { + Contract *Ownable // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// OwnableCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type OwnableCallerSession struct { + Contract *OwnableCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// OwnableTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type OwnableTransactorSession struct { + Contract *OwnableTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// OwnableRaw is an auto generated low-level Go binding around an Ethereum contract. +type OwnableRaw struct { + Contract *Ownable // Generic contract binding to access the raw methods on +} + +// OwnableCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type OwnableCallerRaw struct { + Contract *OwnableCaller // Generic read-only contract binding to access the raw methods on +} + +// OwnableTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type OwnableTransactorRaw struct { + Contract *OwnableTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewOwnable creates a new instance of Ownable, bound to a specific deployed contract. +func NewOwnable(address common.Address, backend bind.ContractBackend) (*Ownable, error) { + contract, err := bindOwnable(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &Ownable{OwnableCaller: OwnableCaller{contract: contract}, OwnableTransactor: OwnableTransactor{contract: contract}, OwnableFilterer: OwnableFilterer{contract: contract}}, nil +} + +// NewOwnableCaller creates a new read-only instance of Ownable, bound to a specific deployed contract. +func NewOwnableCaller(address common.Address, caller bind.ContractCaller) (*OwnableCaller, error) { + contract, err := bindOwnable(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &OwnableCaller{contract: contract}, nil +} + +// NewOwnableTransactor creates a new write-only instance of Ownable, bound to a specific deployed contract. +func NewOwnableTransactor(address common.Address, transactor bind.ContractTransactor) (*OwnableTransactor, error) { + contract, err := bindOwnable(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &OwnableTransactor{contract: contract}, nil +} + +// NewOwnableFilterer creates a new log filterer instance of Ownable, bound to a specific deployed contract. +func NewOwnableFilterer(address common.Address, filterer bind.ContractFilterer) (*OwnableFilterer, error) { + contract, err := bindOwnable(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &OwnableFilterer{contract: contract}, nil +} + +// bindOwnable binds a generic wrapper to an already deployed contract. +func bindOwnable(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := abi.JSON(strings.NewReader(OwnableABI)) + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_Ownable *OwnableRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + return _Ownable.Contract.OwnableCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_Ownable *OwnableRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Ownable.Contract.OwnableTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_Ownable *OwnableRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _Ownable.Contract.OwnableTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_Ownable *OwnableCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + return _Ownable.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_Ownable *OwnableTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Ownable.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_Ownable *OwnableTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _Ownable.Contract.contract.Transact(opts, method, params...) +} + +// Owner is a free data retrieval call binding the contract method 0x8da5cb5b. +// +// Solidity: function owner() constant returns(address) +func (_Ownable *OwnableCaller) Owner(opts *bind.CallOpts) (common.Address, error) { + var ( + ret0 = new(common.Address) + ) + out := ret0 + err := _Ownable.contract.Call(opts, out, "owner") + return *ret0, err +} + +// Owner is a free data retrieval call binding the contract method 0x8da5cb5b. +// +// Solidity: function owner() constant returns(address) +func (_Ownable *OwnableSession) Owner() (common.Address, error) { + return _Ownable.Contract.Owner(&_Ownable.CallOpts) +} + +// Owner is a free data retrieval call binding the contract method 0x8da5cb5b. +// +// Solidity: function owner() constant returns(address) +func (_Ownable *OwnableCallerSession) Owner() (common.Address, error) { + return _Ownable.Contract.Owner(&_Ownable.CallOpts) +} + +// RenounceOwnership is a paid mutator transaction binding the contract method 0x715018a6. +// +// Solidity: function renounceOwnership() returns() +func (_Ownable *OwnableTransactor) RenounceOwnership(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Ownable.contract.Transact(opts, "renounceOwnership") +} + +// RenounceOwnership is a paid mutator transaction binding the contract method 0x715018a6. +// +// Solidity: function renounceOwnership() returns() +func (_Ownable *OwnableSession) RenounceOwnership() (*types.Transaction, error) { + return _Ownable.Contract.RenounceOwnership(&_Ownable.TransactOpts) +} + +// RenounceOwnership is a paid mutator transaction binding the contract method 0x715018a6. +// +// Solidity: function renounceOwnership() returns() +func (_Ownable *OwnableTransactorSession) RenounceOwnership() (*types.Transaction, error) { + return _Ownable.Contract.RenounceOwnership(&_Ownable.TransactOpts) +} + +// TransferOwnership is a paid mutator transaction binding the contract method 0xf2fde38b. +// +// Solidity: function transferOwnership(_newOwner address) returns() +func (_Ownable *OwnableTransactor) TransferOwnership(opts *bind.TransactOpts, _newOwner common.Address) (*types.Transaction, error) { + return _Ownable.contract.Transact(opts, "transferOwnership", _newOwner) +} + +// TransferOwnership is a paid mutator transaction binding the contract method 0xf2fde38b. +// +// Solidity: function transferOwnership(_newOwner address) returns() +func (_Ownable *OwnableSession) TransferOwnership(_newOwner common.Address) (*types.Transaction, error) { + return _Ownable.Contract.TransferOwnership(&_Ownable.TransactOpts, _newOwner) +} + +// TransferOwnership is a paid mutator transaction binding the contract method 0xf2fde38b. +// +// Solidity: function transferOwnership(_newOwner address) returns() +func (_Ownable *OwnableTransactorSession) TransferOwnership(_newOwner common.Address) (*types.Transaction, error) { + return _Ownable.Contract.TransferOwnership(&_Ownable.TransactOpts, _newOwner) +} + +// OwnableOwnershipRenouncedIterator is returned from FilterOwnershipRenounced and is used to iterate over the raw logs and unpacked data for OwnershipRenounced events raised by the Ownable contract. +type OwnableOwnershipRenouncedIterator struct { + Event *OwnableOwnershipRenounced // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *OwnableOwnershipRenouncedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(OwnableOwnershipRenounced) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(OwnableOwnershipRenounced) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *OwnableOwnershipRenouncedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *OwnableOwnershipRenouncedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// OwnableOwnershipRenounced represents a OwnershipRenounced event raised by the Ownable contract. +type OwnableOwnershipRenounced struct { + PreviousOwner common.Address + Raw types.Log // Blockchain specific contextual infos +} + +// FilterOwnershipRenounced is a free log retrieval operation binding the contract event 0xf8df31144d9c2f0f6b59d69b8b98abd5459d07f2742c4df920b25aae33c64820. +// +// Solidity: e OwnershipRenounced(previousOwner indexed address) +func (_Ownable *OwnableFilterer) FilterOwnershipRenounced(opts *bind.FilterOpts, previousOwner []common.Address) (*OwnableOwnershipRenouncedIterator, error) { + + var previousOwnerRule []interface{} + for _, previousOwnerItem := range previousOwner { + previousOwnerRule = append(previousOwnerRule, previousOwnerItem) + } + + logs, sub, err := _Ownable.contract.FilterLogs(opts, "OwnershipRenounced", previousOwnerRule) + if err != nil { + return nil, err + } + return &OwnableOwnershipRenouncedIterator{contract: _Ownable.contract, event: "OwnershipRenounced", logs: logs, sub: sub}, nil +} + +// WatchOwnershipRenounced is a free log subscription operation binding the contract event 0xf8df31144d9c2f0f6b59d69b8b98abd5459d07f2742c4df920b25aae33c64820. +// +// Solidity: e OwnershipRenounced(previousOwner indexed address) +func (_Ownable *OwnableFilterer) WatchOwnershipRenounced(opts *bind.WatchOpts, sink chan<- *OwnableOwnershipRenounced, previousOwner []common.Address) (event.Subscription, error) { + + var previousOwnerRule []interface{} + for _, previousOwnerItem := range previousOwner { + previousOwnerRule = append(previousOwnerRule, previousOwnerItem) + } + + logs, sub, err := _Ownable.contract.WatchLogs(opts, "OwnershipRenounced", previousOwnerRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(OwnableOwnershipRenounced) + if err := _Ownable.contract.UnpackLog(event, "OwnershipRenounced", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// OwnableOwnershipTransferredIterator is returned from FilterOwnershipTransferred and is used to iterate over the raw logs and unpacked data for OwnershipTransferred events raised by the Ownable contract. +type OwnableOwnershipTransferredIterator struct { + Event *OwnableOwnershipTransferred // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *OwnableOwnershipTransferredIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(OwnableOwnershipTransferred) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(OwnableOwnershipTransferred) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *OwnableOwnershipTransferredIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *OwnableOwnershipTransferredIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// OwnableOwnershipTransferred represents a OwnershipTransferred event raised by the Ownable contract. +type OwnableOwnershipTransferred struct { + PreviousOwner common.Address + NewOwner common.Address + Raw types.Log // Blockchain specific contextual infos +} + +// FilterOwnershipTransferred is a free log retrieval operation binding the contract event 0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0. +// +// Solidity: e OwnershipTransferred(previousOwner indexed address, newOwner indexed address) +func (_Ownable *OwnableFilterer) FilterOwnershipTransferred(opts *bind.FilterOpts, previousOwner []common.Address, newOwner []common.Address) (*OwnableOwnershipTransferredIterator, error) { + + var previousOwnerRule []interface{} + for _, previousOwnerItem := range previousOwner { + previousOwnerRule = append(previousOwnerRule, previousOwnerItem) + } + var newOwnerRule []interface{} + for _, newOwnerItem := range newOwner { + newOwnerRule = append(newOwnerRule, newOwnerItem) + } + + logs, sub, err := _Ownable.contract.FilterLogs(opts, "OwnershipTransferred", previousOwnerRule, newOwnerRule) + if err != nil { + return nil, err + } + return &OwnableOwnershipTransferredIterator{contract: _Ownable.contract, event: "OwnershipTransferred", logs: logs, sub: sub}, nil +} + +// WatchOwnershipTransferred is a free log subscription operation binding the contract event 0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0. +// +// Solidity: e OwnershipTransferred(previousOwner indexed address, newOwner indexed address) +func (_Ownable *OwnableFilterer) WatchOwnershipTransferred(opts *bind.WatchOpts, sink chan<- *OwnableOwnershipTransferred, previousOwner []common.Address, newOwner []common.Address) (event.Subscription, error) { + + var previousOwnerRule []interface{} + for _, previousOwnerItem := range previousOwner { + previousOwnerRule = append(previousOwnerRule, previousOwnerItem) + } + var newOwnerRule []interface{} + for _, newOwnerItem := range newOwner { + newOwnerRule = append(newOwnerRule, newOwnerItem) + } + + logs, sub, err := _Ownable.contract.WatchLogs(opts, "OwnershipTransferred", previousOwnerRule, newOwnerRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(OwnableOwnershipTransferred) + if err := _Ownable.contract.UnpackLog(event, "OwnershipTransferred", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} diff --git a/documents/storage.go b/documents/storage.go index 408bb12..51d0462 100644 --- a/documents/storage.go +++ b/documents/storage.go @@ -1,9 +1,9 @@ package documents import ( - "github.com/Bit-Nation/panthalassa/crypto/aes" - "github.com/Bit-Nation/panthalassa/keyManager" - "github.com/asdine/storm" + aes "github.com/Bit-Nation/panthalassa/crypto/aes" + keyManager "github.com/Bit-Nation/panthalassa/keyManager" + storm "github.com/asdine/storm" ) type Document struct { @@ -14,6 +14,9 @@ type Document struct { Content []byte `json:"-"` EncryptedContent aes.CipherText CreatedAt int64 + CID []byte + Signature []byte + TransactionHash string } type Storage struct { diff --git a/keyManager/keyManager.go b/keyManager/keyManager.go index 8a79cc1..bd3aefa 100644 --- a/keyManager/keyManager.go +++ b/keyManager/keyManager.go @@ -4,7 +4,9 @@ import ( "encoding/hex" "encoding/json" "errors" + "strconv" + api "github.com/Bit-Nation/panthalassa/api" aes "github.com/Bit-Nation/panthalassa/crypto/aes" scrypt "github.com/Bit-Nation/panthalassa/crypto/scrypt" ks "github.com/Bit-Nation/panthalassa/keyStore" @@ -14,6 +16,8 @@ import ( identity "github.com/Bit-Nation/panthalassa/keyStore/migration/identity" mnemonic "github.com/Bit-Nation/panthalassa/mnemonic" x3dh "github.com/Bit-Nation/x3dh" + common "github.com/ethereum/go-ethereum/common" + types "github.com/ethereum/go-ethereum/core/types" ethCrypto "github.com/ethereum/go-ethereum/crypto" lp2pCrypto "github.com/libp2p/go-libp2p-crypto" ed25519 "golang.org/x/crypto/ed25519" @@ -22,6 +26,7 @@ import ( type KeyManager struct { keyStore ks.Store account Store + Api *api.API } type Store struct { @@ -277,6 +282,98 @@ func (km KeyManager) AESEncrypt(plainText aes.PlainText) (aes.CipherText, error) return aes.CTREncrypt(plainText, aesSecret) } +func (km KeyManager) SignEthTx(signer types.Signer, addresses common.Address, tx *types.Transaction) (*types.Transaction, error) { + + submittedTx, err := km.Api.SendEthereumTransaction( + tx.Value().String(), + tx.To().String(), + hex.EncodeToString(tx.Data()), + ) + if err != nil { + return nil, err + } + + // convert to ethereum hex string + numToHex := func(txData map[string]interface{}, toTransform string) (string, error) { + + gasPriceStr, ok := txData["gasPrice"].(string) + if ok { + return "", errors.New("gas price must be a string") + } + gasPrice, err := strconv.Atoi(gasPriceStr) + if err != nil { + return "", err + } + + return "0x" + strconv.FormatInt(int64(gasPrice), 16), nil + + } + + // unmarshal tx + var txMap map[string]interface{} + if err := json.Unmarshal([]byte(submittedTx), &txMap); err != nil { + return nil, err + } + + // convert nonce + txMap["nonce"], err = numToHex(txMap, "nonce") + if err != nil { + return nil, err + } + + // convert gas price + txMap["gasPrice"], err = numToHex(txMap, "gasPrice") + if err != nil { + return nil, err + } + + // convert gas + txMap["gas"], err = numToHex(txMap, "gasLimit") + if err != nil { + return nil, err + } + + // convert value + txMap["value"], err = numToHex(txMap, "value") + if err != nil { + return nil, err + } + + // map input + txMap["input"] = txMap["data"] + + // convert signature v + txMap["v"], err = numToHex(txMap, "v") + if err != nil { + return nil, err + } + + // convert signature r + txMap["r"], err = numToHex(txMap, "r") + if err != nil { + return nil, err + } + + // convert signature s + txMap["s"], err = numToHex(txMap, "s") + if err != nil { + return nil, err + } + + // turn mutated transaction into encoded json + txJson, err := json.Marshal(txMap) + if err != nil { + return nil, err + } + + signedTx := &types.Transaction{} + if err := signedTx.UnmarshalJSON(txJson); err != nil { + return nil, err + } + + return signedTx, nil +} + func (km KeyManager) ChatIdKeyPair() (x3dh.KeyPair, error) { strPriv, err := km.keyStore.GetKey(chatMigration.MigrationPrivPrefix) diff --git a/mobile_interface.go b/mobile_interface.go index 2a57279..8472a95 100644 --- a/mobile_interface.go +++ b/mobile_interface.go @@ -1,6 +1,7 @@ package panthalassa import ( + "context" "encoding/base64" "encoding/hex" "encoding/json" @@ -22,11 +23,13 @@ import ( profile "github.com/Bit-Nation/panthalassa/profile" queue "github.com/Bit-Nation/panthalassa/queue" uiapi "github.com/Bit-Nation/panthalassa/uiapi" - "github.com/asdine/storm" - bolt "github.com/coreos/bbolt" + storm "github.com/asdine/storm" + common "github.com/ethereum/go-ethereum/common" + ethclient "github.com/ethereum/go-ethereum/ethclient" proto "github.com/golang/protobuf/proto" log "github.com/ipfs/go-log" ma "github.com/multiformats/go-multiaddr" + bolt "go.etcd.io/bbolt" ) var panthalassaInstance *Panthalassa @@ -59,6 +62,7 @@ func start(dbDir string, km *keyManager.KeyManager, config StartConfig, client, // device api deviceApi := api.New(client) + km.Api = deviceApi // create p2p network p2pNetwork, err := p2p.New() @@ -139,10 +143,42 @@ func start(dbDir string, km *keyManager.KeyManager, config StartConfig, client, // dyncall registry dcr := dyncall.New() - if err := RegisterDocumentCalls(dcr, dbInstance, km); err != nil { + // register document related calls + docStorage := documents.NewStorage(dbInstance, km) + if err := RegisterDocumentCalls(dcr, docStorage, km); err != nil { + return err + } + + ethClient, err := ethclient.Dial(config.EthWsEndpoint) + if err != nil { + return err + } + + var notaryMultiAddr common.Address + + networkID, err := ethClient.NetworkID(context.Background()) + if err != nil { return err } + // make sure network is correct + if networkID.Int64() != int64(4) { + return errors.New("there is only a notary for the rinkeby testnet") + } + + // rinkeby addresses + notaryMultiAddr = common.HexToAddress("0xe4d2032fdda10d4e6f483e2dea6857abc0e3cbf8") + + notaryContract, err := documents.NewNotaryMulti(notaryMultiAddr, ethClient) + if err != nil { + return err + } + notariseCall := documents.NewDocumentNotariseCall(docStorage, km, notaryContract) + if err := dcr.Register(notariseCall); err != nil { + return err + } + + // register contract calls if err := RegisterContactCalls(dcr, dbInstance); err != nil { return err } @@ -158,13 +194,13 @@ func start(dbDir string, km *keyManager.KeyManager, config StartConfig, client, db: dbInstance, dAppStorage: dAppStorage, dyncall: dcr, + chatDB: chatStorage, } return nil } -func RegisterDocumentCalls(dcr *dyncall.Registry, dbInstance *storm.DB, km *keyManager.KeyManager) error { - docStorage := documents.NewStorage(dbInstance, km) +func RegisterDocumentCalls(dcr *dyncall.Registry, docStorage *documents.Storage, km *keyManager.KeyManager) error { // register document all call allCall := documents.NewDocumentAllCall(docStorage) diff --git a/queue/test_utils.go b/queue/test_utils.go index 572a3d1..70f8e1e 100644 --- a/queue/test_utils.go +++ b/queue/test_utils.go @@ -7,7 +7,8 @@ import ( "crypto/rand" "encoding/hex" - bolt "github.com/coreos/bbolt" + + bolt "go.etcd.io/bbolt" ) type testProcessor struct {