Coder Social home page Coder Social logo

nkn-sdk-go's Introduction

nkn-sdk-go

GoDoc GitHub license Go Report Card Build Status PRs Welcome

Go implementation of NKN client and wallet SDK. The SDK consists of a few components:

  • NKN Client: Send and receive data for free between any NKN clients regardless their network condition without setting up a server or relying on any third party services. Data are end to end encrypted by default. Typically you might want to use multiclient instead of using client directly.

  • NKN MultiClient: Send and receive data using multiple NKN clients concurrently to improve reliability and latency. In addition, it supports session mode, a reliable streaming protocol similar to TCP based on ncp.

  • NKN Wallet: Wallet SDK for NKN blockchain. It can be used to create wallet, transfer token to NKN wallet address, register name, subscribe to topic, etc.

Advantages of using NKN client/multiclient for data transmission:

  • Network agnostic: Neither sender nor receiver needs to have public IP address or port forwarding. NKN clients only establish outbound (websocket) connections, so Internet access is all they need. This is ideal for client side peer to peer communication.

  • Top level security: All data are end to end authenticated and encrypted. No one else in the world except sender and receiver can see or modify the content of the data. The same public key is used for both routing and encryption, eliminating the possibility of man in the middle attack.

  • Decent performance: By aggregating multiple overlay paths concurrently, multiclient can get ~100ms end to end latency and 10+mbps end to end session throughput between international devices.

  • Everything is free, open source and decentralized. (If you are curious, node relay traffic for clients for free to earn mining rewards in NKN blockchain.)

Documentation

Full documentation can be found at GoDoc.

Usage

Client

NKN Client provides low level p2p messaging through NKN network. For most applications, it's more suitable to use multiclient (see multiclient section below) for better reliability, lower latency, and session mode support.

Create a client with a generated key pair:

account, err := NewAccount(nil)
client, err := NewClient(account, "", nil)

Or with an identifier (used to distinguish different clients sharing the same key pair):

client, err := NewClient(account, "any string", nil)

Get client key pair:

fmt.Println(account.Seed(), account.PubKey())

Create a client using an existing secret seed:

seed, err := hex.DecodeStrings("039e481266e5a05168c1d834a94db512dbc235877f150c5a3cc1e3903672c673")
account, err := NewAccount(seed)
client, err := NewClient(account, "any string", nil)

Secret seed should be kept SECRET! Never put it in version control system like here.

By default the client will use bootstrap RPC server (for getting node address) provided by NKN. Any NKN full node can serve as a bootstrap RPC server. To create a client using customized bootstrap RPC server:

conf := &ClientConfig{SeedRPCServerAddr: NewStringArray("https://ip:port", "https://ip:port", ...)}
client, err := NewClient(account, "any string", conf)

Get client NKN address, which is used to receive data from other clients:

fmt.Println(client.Address())

Listen for connection established:

<- client.OnConnect.C
fmt.Println("Connection opened.")

Send text message to other clients:

response, err := client.Send(NewStringArray("another client address"), []byte("hello world!"), nil)

You can also send byte array directly:

response, err := client.Send(NewStringArray("another client address"), []byte{1, 2, 3, 4, 5}, nil)

Or publish a message to a specified topic (see wallet section for subscribing to topics):

client.Publish("topic", []byte("hello world!"), nil)

Receive data from other clients:

msg := <- client.OnMessage.C
fmt.Println("Receive message from", msg.Src + ":", string(msg.Payload))
msg.Reply([]byte("response"))

Get 100 subscribers of specified topic starting from 0 offset, including those in tx pool (fetch meta):

subscribers, err := client.GetSubscribers("topic", 0, 100, true, true)
fmt.Println(subscribers.Subscribers, subscribers.SubscribersInTxPool)

Get subscription:

subscription, err := client.GetSubscription("topic", "identifier.publickey")
fmt.Printf("%+v\n", subscription) // &{Meta:meta ExpiresAt:100000}

Multiclient

Multiclient creates multiple client instances by adding identifier prefix (__0__., __1__., __2__., ...) to a nkn address and send/receive packets concurrently. This will greatly increase reliability and reduce latency at the cost of more bandwidth usage (proportional to the number of clients).

Multiclient basically has the same API as client, except for a few more initial configurations:

numSubClients := 3
originalClient := false
multiclient, err := NewMultiClient(account, identifier, numSubClient, originalClient)

where originalClient controls whether a client with original identifier (without adding any additional identifier prefix) will be created, and numSubClients controls how many sub-clients to create by adding prefix __0__., __1__., __2__., etc. Using originalClient == true and numSubClients == 0 is equivalent to using a standard client without any modification to the identifier. Note that if you use originalClient == true and numSubClients is greater than 0, your identifier should not starts with __X__ where X is any number, otherwise you may end up with identifier collision.

Any additional options will be passed to NKN client.

multiclient instance shares the same API as regular NKN client, see above for usage and examples. If you need low-level property or API, you can use multiclient.DefaultClient to get the default client and multiclient.Clients to get all clients.

Session

Multiclient supports a reliable transmit protocol called session. It will be responsible for retransmission and ordering just like TCP. It uses multiple clients to send and receive data in multiple path to achieve better throughput. Unlike regular multiclient message, no redundant data is sent unless packet loss.

Any multiclient can start listening for incoming session where the remote address match any of the given regexp:

multiclient, err := NewMultiClient(...)
// Accepting any address, equivalent to multiclient.Listen(NewStringArray(".*"))
err = multiclient.Listen(nil)
// Only accepting pubkey 25d660916021ab1d182fb6b52d666b47a0f181ed68cf52a056041bdcf4faaf99 but with any identifiers
err = multiclient.Listen(NewStringArray("25d660916021ab1d182fb6b52d666b47a0f181ed68cf52a056041bdcf4faaf99$"))
// Only accepting address alice.25d660916021ab1d182fb6b52d666b47a0f181ed68cf52a056041bdcf4faaf99
err = multiclient.Listen(NewStringArray("^alice\\.25d660916021ab1d182fb6b52d666b47a0f181ed68cf52a056041bdcf4faaf99$"))

Then it can start accepting sessions:

session, err := multiclient.Accept()

Multiclient implements net.Listener interface, so one can use it as a drop-in replacement when net.Listener is needed, e.g. http.Serve.

On the other hand, any multiclient can dial a session to a remote NKN address:

session, err := multiclient.Dial("another nkn address")

Session implements net.Conn interface, so it can be used as a drop-in replacement when net.Conn is needed:

buf := make([]byte, 1024)
n, err := session.Read(buf)
n, err := session.Write(buf)

Wallet

Create wallet SDK:

account, err := NewAccount(nil)
wallet, err := NewWallet(account, &nkn.WalletConfig{Password: "password"})

By default the wallet will use RPC server provided by nkn.org. Any NKN full node can serve as a RPC server. To create a wallet using customized RPC server:

conf := &WalletConfig{
  Password: "password",
  SeedRPCServerAddr: NewStringArray("https://ip:port", "https://ip:port", ...),
}
wallet, err := NewWallet(account, conf)

Export wallet to JSON string, where sensitive contents are encrypted by password provided in config:

walletJSON, err := wallet.ToJSON()

Load wallet from JSON string, note that the password needs to be the same as the one provided when creating wallet:

walletFromJSON, err := nkn.WalletFromJSON(walletJSON, &nkn.WalletConfig{Password: "password"})

Verify whether an address is a valid NKN wallet address:

err := nkn.VerifyWalletAddress(wallet.Address())

Verify password of the wallet:

err := wallet.VerifyPassword("password")

Query asset balance for this wallet:

balance, err := wallet.Balance()
if err == nil {
    log.Println("asset balance:", balance.String())
} else {
    log.Println("query balance fail:", err)
}

Query asset balance for address:

balance, err := wallet.BalanceByAddress("NKNxxxxx")

Transfer asset to some address:

txnHash, err := wallet.Transfer(account.WalletAddress(), "100", nil)

Open nano pay channel to specified address:

// you can pass channel duration (in unit of blocks) after address and txn fee
// after expired new channel (with new id) will be created under-the-hood
// this means that receiver need to claim old channel and reset amount calculation
np, err := wallet.NewNanoPay(address, "0", 4320)

Increment channel balance by 100 NKN:

txn, err := np.IncrementAmount("100")

Then you can pass the transaction to receiver, who can send transaction to on-chain later:

txnHash, err := wallet.SendRawTransaction(txn)

Register name for this wallet:

txnHash, err = wallet.RegisterName("somename", nil)

Delete name for this wallet:

txnHash, err = wallet.DeleteName("somename", nil)

Subscribe to specified topic for this wallet for next 100 blocks:

txnHash, err = wallet.Subscribe("identifier", "topic", 100, "meta", nil)

Unsubscribe from specified topic:

txnHash, err = wallet.Unsubscribe("identifier", "topic", nil)

Compiling to iOS/Android native library

This library is designed to work with gomobile and run natively on iOS/Android without any modification. You can use gomobile bind to compile it to Objective-C framework for iOS:

gomobile bind -target=ios -ldflags "-s -w" github.com/nknorg/nkn-sdk-go github.com/nknorg/ncp-go github.com/nknorg/nkn/v2/transaction github.com/nknorg/nkngomobile

and Java AAR for Android:

gomobile bind -target=android -ldflags "-s -w" github.com/nknorg/nkn-sdk-go github.com/nknorg/ncp-go github.com/nknorg/nkn/v2/transaction github.com/nknorg/nkngomobile

It's recommended to use the latest version of gomobile that supports go modules.

Contributing

Can I submit a bug, suggestion or feature request?

Yes. Please open an issue for that.

Can I contribute patches?

Yes, we appreciate your help! To make contributions, please fork the repo, push your changes to the forked repo with signed-off commits, and open a pull request here.

Please sign off your commit. This means adding a line "Signed-off-by: Name " at the end of each commit, indicating that you wrote the code and have the right to pass it on as an open source patch. This can be done automatically by adding -s when committing:

git commit -s

Community

nkn-sdk-go's People

Contributors

billfort avatar bufrr avatar dependabot[bot] avatar iheron avatar jiangzhiguo1992 avatar omani avatar trueinsider avatar yilunzhang avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

nkn-sdk-go's Issues

Handling WRONG_NODE msg

The Go SDK is currently not handling wrong_node msg. A node might send client a json msg where msg.Error == 48001 (wrong node), and msg.Result containing the new node info client should connect to (same as getwsaddr response). Client should disconnect the current ws connection (if not already) and connect to the new node, otherwise he will not be able to receive msg sent to it.

Get nil message from OnMessage chan

Today I got into a case where <-client.OnMessage returned a nil msg and caused a panic. It should not happen, but I guess it might be related to this part in doc:

Unmarshal parses the protocol buffer representation in buf and places the decoded result in pb. If the struct underlying pb does not match the data in buf, the results can be unpredictable.

Do you have any idea/guess about what happened?

Publish with no subscribers

Hello,

If you publish to a topic with no subscribers, it's throwing the error message "invalid destination" instead of just doing nothing. We are using the go-sdk compiled for Android.

ConnectRetries cannot set 0, will be replaced by default 3

// MergeClientConfig merges a given client config with the default client config
// recursively. Any non zero value fields will override the default config.
func MergeClientConfig(conf *ClientConfig) (*ClientConfig, error) {
	merged := GetDefaultClientConfig()
	if conf != nil {
		err := mergo.Merge(merged, conf, mergo.WithOverride)
		if err != nil {
			return nil, err
		}
	}
	return merged, nil
}

In ClientConfig's defination, 0 means unlimited retries.

// ClientConfig is the client configuration.
type ClientConfig struct {
	...
	ConnectRetries          int32          // Connnect to node retries (including the initial connect). 0 means unlimited retries.
	...
}

Does not work with `go get`

I was trying to write compile a piece of go code importing nkn-sdk-go on a fresh debian. What I did is very simple. First install nkn-sdk-go with "go get"

go get github.com/nknorg/nkn-sdk-go

Then compile a simple go project using nkn-sdk-go and get the following error:

../go/src/github.com/nknorg/nkn/common/common.go:15:2: code in directory /home/xxx/go/src/github.com/golang/crypto/ripemd160 expects import "golang.org/x/crypto/ripemd160"
../go/src/github.com/nknorg/gopass/terminal.go:3:8: code in directory /home/xxx/go/src/github.com/golang/crypto/ssh/terminal expects import "golang.org/x/crypto/ssh/terminal"

I don't think it's a problem of nkn-sdk-go but it will definitely affect people trying out nkn-sdk-go since it's the most convenient way to quickly try it out.

Panic: close of closed channel

Got into this issue today but it's not easy to reproduce. The current code should handle channel close more carefully.

Enter file path to send: 2019/08/17 07:09:23 WRONG NODE TO CONNECT
2019/08/17 07:09:24 write tcp 157.230.136.253:45156->221.235.118.49:30002: use of closed network connection
2019/08/17 07:09:25 write tcp 157.230.136.253:45158->221.235.118.49:30002: use of closed network connection
2019/08/17 07:09:28 write tcp 157.230.136.253:45160->221.235.118.49:30002: use of closed network connection

...

2019/08/17 07:11:26 write tcp 157.230.136.253:45320->221.235.118.49:30002: use of closed network connection
2019/08/17 07:11:29 write tcp 157.230.136.253:45322->221.235.118.49:30002: use of closed network connection
2019/08/17 07:11:31 write tcp 157.230.136.253:45324->221.235.118.49:30002: use of closed network connection
2019/08/17 07:11:36.439565 [ERROR] GID 6701, POST request: Post http://mainnet-seed-0015.nkn.org:30003: net/http: request canceled (Client.Timeout exceeded while awaiting headers)

2019/08/17 07:11:36 unexpected end of JSON input
panic: close of closed channel

goroutine 6701 [running]:
github.com/nknorg/nkn-sdk-go.(*Client).connect.func2.1(0xc0000dd450)
	/root/go/src/github.com/nknorg/nkn-sdk-go/client.go:143 +0x32
github.com/nknorg/nkn-sdk-go.(*Client).connect.func2(0xc0000dd450, 0xc00042f080)
	/root/go/src/github.com/nknorg/nkn-sdk-go/client.go:250 +0xb9
created by github.com/nknorg/nkn-sdk-go.(*Client).connect
	/root/go/src/github.com/nknorg/nkn-sdk-go/client.go:141 +0x1d0
exit status 2

Concurrent write to websocket connection

The following error may occur when sending multiple messages concurrently. As mentioned in the doc https://godoc.org/github.com/gorilla/websocket#hdr-Concurrency we need to add mutex to make sure we don't call writeMessage concurrently.

panic: concurrent write to websocket connection

goroutine 59 [running]:
github.com/gorilla/websocket.(*messageWriter).flushFrame(0xc0001419e0, 0xc000067d01, 0x0, 0x0, 0x0, 0x64, 0x3)
	/Users/skysniper/go/pkg/mod/github.com/gorilla/[email protected]/conn.go:591 +0x77a
github.com/gorilla/websocket.(*messageWriter).Close(0xc0001419e0, 0x64, 0xc000067d70)
	/Users/skysniper/go/pkg/mod/github.com/gorilla/[email protected]/conn.go:709 +0x56
github.com/gorilla/websocket.(*Conn).prepWrite(0xc0002242c0, 0x2, 0x70, 0x1629b60)
	/Users/skysniper/go/pkg/mod/github.com/gorilla/[email protected]/conn.go:459 +0x20d
github.com/gorilla/websocket.(*Conn).NextWriter(0xc0002242c0, 0x2, 0x68, 0xc00000f400, 0xc00007e4d0, 0x68)
	/Users/skysniper/go/pkg/mod/github.com/gorilla/[email protected]/conn.go:494 +0x39
github.com/gorilla/websocket.(*Conn).WriteMessage(0xc0002242c0, 0x2, 0xc00007e4d0, 0x68, 0x68, 0x0, 0x0)
	/Users/skysniper/go/pkg/mod/github.com/gorilla/[email protected]/conn.go:755 +0x73
github.com/nknorg/nkn-sdk-go.(*Client).sendReceipt(0xc0001bc000, 0xc0000d31c0, 0x20, 0x20, 0xc000096420, 0x0)
	/Users/skysniper/go/pkg/mod/github.com/nknorg/[email protected]/client.go:281 +0x407
github.com/nknorg/nkn-sdk-go.(*Client).connect.func2.2.1(0xc0001bc000, 0xc0002ee000)
	/Users/skysniper/go/pkg/mod/github.com/nknorg/[email protected]/client.go:200 +0x4f
created by github.com/nknorg/nkn-sdk-go.(*Client).connect.func2.2
	/Users/skysniper/go/pkg/mod/github.com/nknorg/[email protected]/client.go:199 +0x2e5

Issue running examples

Issue : enable to run examples
./client.go:172:15: cannot use dests (type map[string]string) as type []string in argument to c.Send
I did cloned nkn last master release, and nkn-sdk-go but non of the examples run both fails at c.Send
with error above

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.