Cant use jwt connect nats by my code,its doesnt work - websocket

I want to use Nats websocket but I don't know how to get user information so I use jwt and it works but it doesn't work when I use my code to generate user jwt.
nats server cfg
websocket
{
port: 8080
no_tls: true
# authorization {
# # If this is specified, the client has to provide the same username
# # and password to be able to connect.
# # username: "my_user_name"
# # password: "my_password"
#
# # If this is specified, the password field in the CONNECT has to
# # match this token.
# # token: "my_token"
#
# # This overrides the main's authorization timeout. For consistency
# # with the main's authorization configuration block, this is expressed
# # as a number of seconds.
# # timeout: 2.0
#}
}
# Operator named my_org
operator: eyJ0eXAiOiJKV1QiLCJhbGciOiJlZDI1NTE5LW5rZXkifQ.eyJqdGkiOiI3VVkzSkNaVUlVQUU0STZJWllBQ0NZUEdDWDdQQUlHRzczQklNTzVBT1NZMjJXQ1JZWDdRIiwiaWF0IjoxNjY4NDE3NDkzLCJpc3MiOiJPQzRLV0xGNU5CQkhPVjVLRFJGS0NITFkySEpBRFNYQUg2VEJHSk1XSEFVSFg3VlVOTkNTRVdJUyIsIm5hbWUiOiJteV9vcmciLCJzdWIiOiJPQzRLV0xGNU5CQkhPVjVLRFJGS0NITFkySEpBRFNYQUg2VEJHSk1XSEFVSFg3VlVOTkNTRVdJUyIsIm5hdHMiOnsic2lnbmluZ19rZXlzIjpbIk9DUFdTV0NQSlJNMlRDUjVIVjZFNkNFVklRV09MNExLNEJPS1VQRk5DNzNJVktZM0xIN0VVRTZZIl0sImFjY291bnRfc2VydmVyX3VybCI6Im5hdHM6Ly8wLjAuMC4wOjQyMjIiLCJzeXN0ZW1fYWNjb3VudCI6IkFDU0dDWENUVFpLWlVCRkFIN1lFR01HTkhQRFRPQlRJRUdONFlHS1JWT1hXT1FOM1Y2T1NVS1Q1Iiwic3RyaWN0X3NpZ25pbmdfa2V5X3VzYWdlIjp0cnVlLCJ0eXBlIjoib3BlcmF0b3IiLCJ2ZXJzaW9uIjoyfX0.axLP53rM3O2R6XNMagyX4vnBoYCp7DCA2lptVlX2i4lLdbN9x5Vm4eYP-7yG7kMqDG9rPG6HmgCyYoQndqpuAw
# System Account named SYS
system_account: ACSGCXCTTZKZUBFAH7YEGMGNHPDTOBTIEGN4YGKRVOXWOQN3V6OSUKT5
# configuration of the nats based resolver
resolver {
type: full
# Directory in which the account jwt will be stored
dir: './jwt'
# In order to support jwt deletion, set to true
# If the resolver type is full delete will rename the jwt.
# This is to allow manual restoration in case of inadvertent deletion.
# To restore a jwt, remove the added suffix .delete and restart or send a reload signal.
# To free up storage you must manually delete files with the suffix .delete.
allow_delete: false
# Interval at which a nats-server with a nats based account resolver will compare
# it's state with one random nats based account resolver in the cluster and if needed,
# exchange jwt and converge on the same set of jwt.
interval: "2m"
# Timeout for lookup requests in case an account does not exist locally.
timeout: "1.9s"
}
# Preload the nats based resolver with the system account jwt.
# This is not necessary but avoids a bootstrapping system account.
# This only applies to the system account. Therefore other account jwt are not included here.
# To populate the resolver:
# 1) make sure that your operator has the account server URL pointing at your nats servers.
# The url must start with: "nats://"
# nsc edit operator --account-jwt-server-url nats://localhost:4222
# 2) push your accounts using: nsc push --all
# The argument to push -u is optional if your account server url is set as described.
# 3) to prune accounts use: nsc push --prune
# In order to enable prune you must set above allow_delete to true
# Later changes to the system account take precedence over the system account jwt listed here.
resolver_preload: {
ACSGCXCTTZKZUBFAH7YEGMGNHPDTOBTIEGN4YGKRVOXWOQN3V6OSUKT5: eyJ0eXAiOiJKV1QiLCJhbGciOiJlZDI1NTE5LW5rZXkifQ.eyJqdGkiOiJMWUI0S0tCN0dKQTYyQTZMVk1BS1hFRlRWUE1DUkRQQVhBNktBSEZNTlZWWkIzSktCVlJRIiwiaWF0IjoxNjY4NDE3NDY5LCJpc3MiOiJPQ1BXU1dDUEpSTTJUQ1I1SFY2RTZDRVZJUVdPTDRMSzRCT0tVUEZOQzczSVZLWTNMSDdFVUU2WSIsIm5hbWUiOiJTWVMiLCJzdWIiOiJBQ1NHQ1hDVFRaS1pVQkZBSDdZRUdNR05IUERUT0JUSUVHTjRZR0tSVk9YV09RTjNWNk9TVUtUNSIsIm5hdHMiOnsiZXhwb3J0cyI6W3sibmFtZSI6ImFjY291bnQtbW9uaXRvcmluZy1zdHJlYW1zIiwic3ViamVjdCI6IiRTWVMuQUNDT1VOVC4qLlx1MDAzZSIsInR5cGUiOiJzdHJlYW0iLCJhY2NvdW50X3Rva2VuX3Bvc2l0aW9uIjozLCJkZXNjcmlwdGlvbiI6IkFjY291bnQgc3BlY2lmaWMgbW9uaXRvcmluZyBzdHJlYW0iLCJpbmZvX3VybCI6Imh0dHBzOi8vZG9jcy5uYXRzLmlvL25hdHMtc2VydmVyL2NvbmZpZ3VyYXRpb24vc3lzX2FjY291bnRzIn0seyJuYW1lIjoiYWNjb3VudC1tb25pdG9yaW5nLXNlcnZpY2VzIiwic3ViamVjdCI6IiRTWVMuUkVRLkFDQ09VTlQuKi4qIiwidHlwZSI6InNlcnZpY2UiLCJyZXNwb25zZV90eXBlIjoiU3RyZWFtIiwiYWNjb3VudF90b2tlbl9wb3NpdGlvbiI6NCwiZGVzY3JpcHRpb24iOiJSZXF1ZXN0IGFjY291bnQgc3BlY2lmaWMgbW9uaXRvcmluZyBzZXJ2aWNlcyBmb3I6IFNVQlNaLCBDT05OWiwgTEVBRlosIEpTWiBhbmQgSU5GTyIsImluZm9fdXJsIjoiaHR0cHM6Ly9kb2NzLm5hdHMuaW8vbmF0cy1zZXJ2ZXIvY29uZmlndXJhdGlvbi9zeXNfYWNjb3VudHMifV0sImxpbWl0cyI6eyJzdWJzIjotMSwiZGF0YSI6LTEsInBheWxvYWQiOi0xLCJpbXBvcnRzIjotMSwiZXhwb3J0cyI6LTEsIndpbGRjYXJkcyI6dHJ1ZSwiY29ubiI6LTEsImxlYWYiOi0xfSwic2lnbmluZ19rZXlzIjpbIkFEU05HUk5WRUhIWFU0SFdUNk80NTQyVFVLSlVER0ZCNU9DTzZYQTNHVE9NTklBMjMyUU9LQzRFIl0sImRlZmF1bHRfcGVybWlzc2lvbnMiOnsicHViIjp7fSwic3ViIjp7fX0sInR5cGUiOiJhY2NvdW50IiwidmVyc2lvbiI6Mn19.DTH_ubEJpwPIj2tmr1eg8nI_HgKvFFqhQ0iL17fT8iy1bJ1AR_jnXg7CKNakYQrdb4pjEBzzpMoH_mbguSdGAQ,
}
When i use nsc client tools its work,
nsc add user --account TEAM_B math
nsc generate creds -n math > math.creds
When i use my code it doesnt work
package main
import (
"github.com/nats-io/jwt/v2"
"github.com/nats-io/nats.go"
"github.com/nats-io/nkeys"
"time"
)
func main() {
ukp, err := nkeys.CreateUser()
if err != nil {
return
}
upub, err := ukp.PublicKey()
if err != nil {
return
}
seed, err := ukp.Seed()
if err != nil {
return
}
akp, _ := nkeys.FromSeed([]byte("SAAFREANAV7DLYTGDCST76AHUOAMK7CTK5RNJWPERHWEFPR7NXEHRTHUWI"))
userJWT := generateUserJWT(upub, akp)
jwtAuthOption := nats.UserJWTAndSeed(userJWT, string(seed))
nc, err := nats.Connect("nats://localhost:4222", jwtAuthOption)
if err != nil {
panic(err)
}
defer nc.Close()
}
func generateUserJWT(userPublicKey string, accountSigningKey nkeys.KeyPair) (userJWT string) {
uc := jwt.NewUserClaims(userPublicKey)
uc.Expires = time.Now().Add(time.Hour).Unix() // expire in an hour
var err error
uc.IssuerAccount, err = accountSigningKey.PublicKey()
if err != nil {
return ""
}
vr := jwt.ValidationResults{}
uc.Validate(&vr)
if vr.IsBlocking(true) {
panic("Generated user claim is invalid")
}
userJWT, err = uc.Encode(accountSigningKey)
if err != nil {
return ""
}
return
}
here is refence
https://docs.nats.io/running-a-nats-service/nats_admin/security/jwt#create-user-jwt
https://github.com/ConnectEverything/rethink_connectivity_examples/tree/main/episode7
my code generate jwt
its panic Authorization Violation
here is my FE code
import './App.css';
import {connect, jwtAuthenticator,credsAuthenticator, StringCodec} from 'nats.ws'
import {useEffect, useState} from "react";
const sc = StringCodec()
function App() {
const c=`-----BEGIN NATS USER JWT-----
eyJ0eXAiOiJKV1QiLCJhbGciOiJlZDI1NTE5LW5rZXkifQ.eyJqdGkiOiJWNFRMQU9aQjY2M1NJT0JCV1RCVkpWVVJOQkdLTUJBUERGVVk2WVUzM1JHQTRXRFBSSkdBIiwiaWF0IjoxNjY4NDkxODE4LCJpc3MiOiJBQTVZTzRRQVVQREhEWVFQR1lKMlRQN1dEV1RGVkNHUlVXVzdFSTVLQUhHM1RRWlVHMkdRVTZYSyIsIm5hbWUiOiJteS11c2VyIiwic3ViIjoiVURXRkFQVVUzWlAyQ1VFVkRWWkpBRjJRM0hLNlJLSU5YS0xQWjNYV0VFWkxJRE00RjdWMzZXM0MiLCJuYXRzIjp7InB1YiI6eyJhbGxvdyI6WyJmb28uXHUwMDNlIiwiYmFyLlx1MDAzZSJdfSwic3ViIjp7ImFsbG93IjpbIl9JTkJPWC5cdTAwM2UiXX0sInN1YnMiOi0xLCJkYXRhIjoxMDczNzQxODI0LCJwYXlsb2FkIjotMSwidHlwZSI6InVzZXIiLCJ2ZXJzaW9uIjoyfX0.6Zg8ekHENudDY2gT5hVfXomnQ1tGfHT7O__FrewjWXH3oaWPy81Qr7_U1ZzmuWPirTq4JsZjoOnV9TxmrwywCA
------END NATS USER JWT------
************************* IMPORTANT *************************
NKEY Seed printed below can be used to sign and prove identity.
NKEYs are sensitive and should be treated as secrets.
-----BEGIN USER NKEY SEED-----
SUAIRSUPV65OG3S5C66DIMLNY2IXNUSHT6QEBFMWXGBM7G3EGPJO3XHELE
------END USER NKEY SEED------
*************************************************************
`
const [nc, setConnection] = useState(undefined)
const [lastError, setError] = useState("")
const [messages, setMessages] = useState([])
let key = 0
const me = {id:"dddd",name:"ff"};
const addMessage = (err, msg) => {
if (err){
console.log(err)
}
key++;
const {subject, reply} = msg;
const data = sc.decode(msg.data)
console.log("msg==",subject,' data=',data)
const m = {subject, reply, data, key, time: new Date().toUTCString()}
messages.unshift(m)
const a = messages.slice(0, 10)
messages.unshift(a)
setMessages(a)
}
const who = (err,msg)=>{
msg.respond(me)
const {subject, reply} = msg;
const data = sc.decode(msg.data)
console.log("who==",subject,' data=',data)
}
const entered = (err,msg)=>{
const {subject, reply} = msg;
const data = sc.decode(msg.data)
console.log("en==",subject,' data=',data)
}
const exited = (err,msg)=>{
const {subject, reply} = msg;
const data = sc.decode(msg.data)
console.log("exit==",subject,' data=',data)
}
useEffect(() => {
console.log(nc)
if (nc === undefined) {
//connect({servers: ["nats://127.0.0.1:4222"],
connect({servers: ["ws://127.0.0.1:8083"],
//work //authenticator:jwtAuthenticator("eyJ0eXAiOiJKV1QiLCJhbGciOiJlZDI1NTE5LW5rZXkifQ.eyJqdGkiOiJCQ1kyRjUyRUxIU1kzV0RYUkhUU0FLUjU2TEZCWU4yUjdNSU5CWENNUUVZQ0JJNUVXQzNBIiwiaWF0IjoxNjY4NDE3NTI2LCJpc3MiOiJBQkRVWE9WTkdLV1o1QUlGR1g1VUdRSFJCUVo3S0dIWk9URDdOVTRWTVBKU1BRTkdXNkJNQjJQTCIsIm5hbWUiOiJtYXRoIiwic3ViIjoiVUE0RTNSRVU1Nlo3MjM2U0hTUURQSUY1UlhWRzJXVlkzV0NPRlJGRENSQkRTWkNWQkJPRFJQVkgiLCJuYXRzIjp7InB1YiI6e30sInN1YiI6e30sInN1YnMiOi0xLCJkYXRhIjotMSwicGF5bG9hZCI6LTEsImlzc3Vlcl9hY2NvdW50IjoiQUNYRElYRElJSVVUT1YzWDZITVVWSEUyTEJNUDI0N1FHS0IyWkFCQTdWSElGWjdKRU1TVUxTSkwiLCJ0eXBlIjoidXNlciIsInZlcnNpb24iOjJ9fQ.tDfuREQDIFiIOlAD1fe7jkrVPiaRSoAwcRa_e4G3AVby97XSssEN_EQCeT60WomOo1fHIFV9hgMCuPHQAaL_Ag", new TextEncoder().encode("SUALJXSMUDYDDKWMWLREDHEEBA7HZA5FJVJFVUYMELSQNT2BJJ3J665RDQ")),
//work //authenticator:jwtAuthenticator("eyJ0eXAiOiJKV1QiLCJhbGciOiJlZDI1NTE5LW5rZXkifQ.eyJqdGkiOiJUMlI3RURSVVNESUxJUUNKVFRGUVhLNVJaRUsyNlFPWURFNFozQzdQREFKUVRJWkhQUllRIiwiaWF0IjoxNjY4NDgyOTA5LCJpc3MiOiJBQkRVWE9WTkdLV1o1QUlGR1g1VUdRSFJCUVo3S0dIWk9URDdOVTRWTVBKU1BRTkdXNkJNQjJQTCIsIm5hbWUiOiJtYXRoMTEiLCJzdWIiOiJVQUw2SFdGQlhBVUFBUE1LR1RYM1JSVVkySFc3QVZXRVBDUFBIUFlMWU9VM1oyVTVPQzZLSDdFWiIsIm5hdHMiOnsicHViIjp7fSwic3ViIjp7fSwic3VicyI6LTEsImRhdGEiOi0xLCJwYXlsb2FkIjotMSwiaXNzdWVyX2FjY291bnQiOiJBQ1hESVhESUlJVVRPVjNYNkhNVVZIRTJMQk1QMjQ3UUdLQjJaQUJBN1ZISUZaN0pFTVNVTFNKTCIsInR5cGUiOiJ1c2VyIiwidmVyc2lvbiI6Mn19.7HovnuwbJvQCjiofMLjlT_ASa2k2xA8_biCOx-KWbGcj11kptVSsFZHKqm6ppg3OM8klNvCwDNJhJHhx0U8uAQ", new TextEncoder().encode("SUACFBD4BOQ4AWR2BG5SPLOJXFIFSWPCSY3ZS25YN5KLQXD5QKBJNMUNQQ")),
// my jwt doesn't work
authenticator:jwtAuthenticator("eyJ0eXAiOiJKV1QiLCJhbGciOiJlZDI1NTE5LW5rZXkifQ.eyJleHAiOjE2Njg1MDg2OTgsImp0aSI6IkZVMkhPRjJLMkNaWU42UEtZNTNWQUQyUlpXVjdXSEJMTzJEVVhHUzVZVEFSS1BYQjcyWkEiLCJpYXQiOjE2Njg1MDUwOTksImlzcyI6IkFDU0dDWENUVFpLWlVCRkFIN1lFR01HTkhQRFRPQlRJRUdONFlHS1JWT1hXT1FOM1Y2T1NVS1Q1Iiwic3ViIjoiVUFTV0NVSzVCWVpSTVRVU0ZBVjNFT1pFUzRSMzNWRkc1REZKVUtKM1ZLSUtQTDZCNUEyNFI1UkciLCJuYXRzIjp7InB1YiI6e30sInN1YiI6e30sInN1YnMiOi0xLCJkYXRhIjotMSwicGF5bG9hZCI6LTEsImlzc3Vlcl9hY2NvdW50IjoiQUNTR0NYQ1RUWktaVUJGQUg3WUVHTUdOSFBEVE9CVElFR040WUdLUlZPWFdPUU4zVjZPU1VLVDUiLCJ0eXBlIjoidXNlciIsInZlcnNpb24iOjJ9fQ.6-pWmq1QUkmDGH6pZuXSm6pgE_VgHoJjukCMIQN6p3j5vFV5YrRWO48IDzKobUm1De4wkZHgGJZiFctM2PpDAA", new TextEncoder().encode("SUAFPCMKO6X6K2Z4GHKK7OXCLI3Q7VWOCJBIROLTX6ILSW2W7HZBJEDBJ4")),
//authenticator:credsAuthenticator(new TextEncoder().encode(c)),
waitOnFirstConnect: true,noEcho:true,
}).then(
(nc) => {
setConnection(nc)
nc.subscribe('>', {callback: addMessage})
nc.subscribe('user.who',{callback:who})
nc.subscribe('user.*.entered',{callback:entered})
nc.subscribe('user.*.exit',{callback:exited})
nc.publish('user.bob#bob.com.entered',sc.encode(me))
}
).catch((err) => {
setError(err)
console.log(lastError)
})
}
})
const state = nc ? 'connected' : "not yet con"
return (
<div className="container">
<h1>{state}</h1>
</div>
);
}
export default App;

Just to make sure: did you use nsc push your changes after adding user math? You configured your nats server with a resolver configuration. This is good, but your resolver needs the account information to verify your user.

Related

Correct way to provide minimal browser-api to Go wasm and returning lib-api back to client, without Global scope?

I'm trying to find a best/right way to implement minimal and safe client <-> wasm api while avoiding usage of window|global|self context. I have found 3 working solutions so far which I wrote here as simplified self explaining example. I'm not sure what's the right solution and most likely there are besides these approaches 4th, 5th, nth solutions. Has anybody found needle in a haystack solution?
// main.go
package main
import (
"syscall/js"
"unsafe"
)
// this would be needed to be here to satisfy this error?
// go:linkname only allowed in Go files that import "unsafe"
var _ unsafe.Pointer
//go:linkname jsGo syscall/js.jsGo
var jsGo js.Value
type ref uint32
type tmpValue struct {
_ [0]func()
ref ref
gcPtr *ref
}
func hello(this js.Value, args []js.Value) any {
println("hello " + this.Get("name").String())
return nil
}
func main() {
c := make(chan struct{}, 0)
jsapi1 := js.Global() // not a window anymore
client1 := jsapi1.Get("client")
println("replace Global:", client1.Get("name").String())
jsapi2 := jsGo.Get("jsapi")
client2 := jsapi2.Get("client")
println("jsGo.client:", client2.Get("name").String())
tmp := (*tmpValue)(unsafe.Pointer(&jsapi1))
tmp.ref = 7
jsapi3 := (*js.Value)(unsafe.Pointer(tmp))
client3 := jsapi3.Get("client")
println("hack the table:", client3.Get("name").String())
client3.Set("hello", js.FuncOf(hello))
jsapi3.Get("Object").Call("freeze", client3)
<-c
}
// go.js
export class Go {
async run (instance, clientClass = name => ({ name })) {
/* ... */
const createJsAPI = (client) => ({
client, // client api not in global wid
Object, // browser api's want to provide for wasm.
})
// 1 e.g. replace js.Global
const replaceGlobalThis = createJsAPI(clientClass('client 1'))
// 2 e.g. add minimal api to syscall/js.jsGo
this.jsapi = createJsAPI(clientClass('client 2'))
// 3 e.g. add new entry ref
const jsapi = createJsAPI(clientClass('client 3'))
this._values = [ NaN, 0, null, true, false, replaceGlobalThis, this, jsapi ]
this._ids = new Map([[0, 1], [null, 2], [true, 3], [false, 4], [replaceGlobalThis, 5], [this, 6], [jsapi, 7]])
/* ... */
}
}
// client could also be wrapped with Proxy
// new Proxy({}, { get: (_, key) => {} } )
export const Client = name => ({ name })
client
// index.js
import { Go, Client } from 'go.js'
const go = new Go()
const client = Client('client 3')
const wasm = await WebAssembly.instantiateStreaming(fetch(wasmUrl), go.importObject)
go.run(wasm.instance, client)
client.hello()
client.x = 1
OUTPUT:
replace Global: client 1
jsGo.client: client 2
hack the table: client 3
hello client 3
Error: TypeError: Cannot add property x, object is not extensible // as expected

Rust Actix Actor send message to actor

How to send a message to another actor?
pub struct MyWs {
}
impl Actor for MyWs {
type Context = ws::WebsocketContext<Self>;
}
impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for MyWs {
fn handle(&mut self, msg: Result<ws::Message, ws::ProtocolError>, ctx: &mut Self::Context) {
match msg {
Ok(ws::Message::Ping(msg)) => ctx.pong(&msg),
Ok(ws::Message::Text(message)) => {
//considering that here he sent the message to self
ctx.text(message);
//how to do something like this
//find the actor by index (or uuid) and send text
//actors[0].text(message);
//
},
Ok(ws::Message::Binary(bin)) => ctx.binary(bin),
Ok(ws::Message::Close(reason)) => ctx.close(reason),
_ => (),
}
}
}
#[get("/ws")]
pub async fn websocket(req: HttpRequest, stream: web::Payload,) -> actix_web::Result<HttpResponse> {
let resp = ws::start(
MyWs {},
&req,
stream,
);
return resp;
}
Could I make a hashMap of actors?
pub struct MyWs { sessions: HashMap<Uuid, Socket> }
and later
self.sessions.text(message)
I'm new to rust and I don't see a way to save the socket (the context or actor) to find it and send it messages.
You might want to check the official example of a chat room app using actix web.
https://github.com/actix/examples/blob/743af0ff1a9be6fb1cc13e6583108463c89ded4d/websockets/chat/src/main.rs
There are three key points:
create another server actor and get the address of it.
let server = server::ChatServer::new(app_state.clone()).start();
set the server actor's address as application data of HttpServer.
HttpServer::new(move || {
App::new()
.data(app_state.clone())
// copy server actor's address into app
.data(server.clone())
.service(web::resource("/").route(web::get().to(|| {
HttpResponse::Found()
.header("LOCATION", "/static/websocket.html")
.finish()
})))
.route("/count/", web::get().to(get_count))
// websocket
.service(web::resource("/ws/").to(chat_route))
// static resources
.service(fs::Files::new("/static/", "static/"))
})
.bind("127.0.0.1:8080")?
.run()
.await
store the address while starting websocket actor in function chat_route. And then you could use the address to send messages whenever you want
/// Entry point for our websocket route
async fn chat_route(
req: HttpRequest,
stream: web::Payload,
srv: web::Data<Addr<server::ChatServer>>,
) -> Result<HttpResponse, Error> {
ws::start(
WsChatSession {
id: 0,
hb: Instant::now(),
room: "Main".to_owned(),
name: None,
addr: srv.get_ref().clone(),
},
&req,
stream,
)
}

How can I make a batch send transaction?

I'm having trouble sending tokens to many addresses simultaneously. As far as I know, Cosmos does not support batch sending, so I have to make one tx for each recipient, and make sure the account sequence (nonce) is correct for each tx.
So if I specify the account sequence manually, I can create many send transactions at once - but I still have to wait for the node to confirm one tx and update the account sequence before sending the next one.
What can one do in this case?
Cosmos actually does support a MultiSend operation on the bank module, unfortunately it is not wired into the clients for support typically however it is widely used by exhanges such as Coinbase to optimize transfers.
https://github.com/cosmos/cosmos-sdk/blob/e957fad1a7ffd73712cd681116c9b6e09fa3e60b/x/bank/keeper/msg_server.go#L73
I have since been reminded that a single Cosmos transaction can consist of multiple Messages, where each Message is a message to the bank module to send tokens to one account. That's how batch sending works in Cosmos.
Unfortunately this is not available via the command line, so one has to use the SDK to make such a transaction.
Here's a link to CosmJS demonstration of sending a transaction with two messages and two signatures on the v0.39.x LTS release of the Cosmos SDK here (hat tip Ethan Frey from Confio for pointing this out)
In case the link changes at any point here's the code:
describe("appendSignature", () => {
it("works", async () => {
pendingWithoutLaunchpad();
const wallet0 = await Secp256k1HdWallet.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(0));
const wallet1 = await Secp256k1HdWallet.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(1));
const client0 = new SigningCosmosClient(launchpad.endpoint, faucet.address0, wallet0);
const client1 = new SigningCosmosClient(launchpad.endpoint, faucet.address1, wallet1);
const msg1: MsgSend = {
type: "cosmos-sdk/MsgSend",
value: {
from_address: faucet.address0,
to_address: makeRandomAddress(),
amount: coins(1234567, "ucosm"),
},
};
const msg2: MsgSend = {
type: "cosmos-sdk/MsgSend",
value: {
from_address: faucet.address1,
to_address: makeRandomAddress(),
amount: coins(1234567, "ucosm"),
},
};
const fee = {
amount: coins(2000, "ucosm"),
gas: "160000", // 2*80k
};
const memo = "This must be authorized by the two of us";
const signed = await client0.sign([msg1, msg2], fee, memo);
const cosigned = await client1.appendSignature(signed);
expect(cosigned.msg).toEqual([msg1, msg2]);
expect(cosigned.fee).toEqual(fee);
expect(cosigned.memo).toEqual(memo);
expect(cosigned.signatures).toEqual([
{
pub_key: faucet.pubkey0,
signature: jasmine.stringMatching(base64Matcher),
},
{
pub_key: faucet.pubkey1,
signature: jasmine.stringMatching(base64Matcher),
},
]);
// Ensure signed transaction is valid
const broadcastResult = await client0.broadcastTx(cosigned);
assertIsBroadcastTxSuccess(broadcastResult);
});
});
For doing this kind of thing in Golang, like with the Cosmos SDK client CLI, you can take the distribution module as an example: https://github.com/cosmos/cosmos-sdk/blob/master/x/distribution/client/cli/tx.go#L165-L180
msgs := make([]sdk.Msg, 0, len(validators))
for _, valAddr := range validators {
val, err := sdk.ValAddressFromBech32(valAddr)
if err != nil {
return err
}
msg := types.NewMsgWithdrawDelegatorReward(delAddr, val)
if err := msg.ValidateBasic(); err != nil {
return err
}
msgs = append(msgs, msg)
}
chunkSize, _ := cmd.Flags().GetInt(FlagMaxMessagesPerTx)
return newSplitAndApply(tx.GenerateOrBroadcastTxCLI, clientCtx, cmd.Flags(), msgs, chunkSize)

Rabbitmq RPC implementation share the same reply queue

I am trying to build RPC by using rabbitmq.
According to the tutorial for building RPC through rabbitmq http://www.rabbitmq.com/tutorials/tutorial-six-ruby.html, we can use the one reply-queue for each client and use correlation_id to map the response and request. I am confused about how the correlation_id is used?
Here is the issue I am running with, I would like to create two rpc calls synchronously from one client, using the same reply-queue with two different correlation id. However I don't know if this is the correct use case, since from what I read in the tutorial it seems to assume that each client is making the rpc call sequentially. (In this case, it becomes more confusing that why would we need the correlation_id here).
Here is the code example (the rpc_server.rb will be the same as the tutorial), for what I am trying to achieve. Hopefully it makes my question more clear.
The code block below wouldn't work, since the correlation_id is being overwrite by thr2 when we set it in thr1.
I wonder if there is anyway to modify it, to make it work? If we try to move the #reply_queue.subscribe block out of initialization and pass in different call_id, it still doesn't work since it seem like the #reply-queue will be locked while waiting for thr1 to finish.
Please let me know if the question is uncleared, thanks in advance for any of your response.
#!/usr/bin/env ruby
# encoding: utf-8
require "bunny"
require "thread"
conn = Bunny.new(:automatically_recover => false)
conn.start
ch = conn.create_channel
class FibonacciClient
attr_reader :reply_queue
attr_accessor :response, :call_id
attr_reader :lock, :condition
def initialize(ch, server_queue)
#ch = ch
#x = ch.default_exchange
#server_queue = server_queue
#reply_queue = ch.queue("", :exclusive => true)
#lock = Mutex.new
#condition = ConditionVariable.new
that = self
#reply_queue.subscribe do |delivery_info, properties, payload|
if properties[:correlation_id] == that.call_id
that.response = payload.to_i
that.lock.synchronize{that.condition.signal}
end
end
end
def call(n)
self.call_id = self.generate_uuid
#x.publish(n.to_s,
:routing_key => #server_queue,
:correlation_id => call_id,
:reply_to => #reply_queue.name)
lock.synchronize{condition.wait(lock)}
response
end
protected
def generate_uuid
# very naive but good enough for code
# examples
"#{rand}#{rand}#{rand}"
end
end
client = FibonacciClient.new(ch, "rpc_queue")
thr1 = Thread.new{
response1 = client.call(30)
puts response1
}
thr2 = Thread.new{
response2 = client.call(40)
puts response2
}
ch.close
conn.close
same problem here.
'use strict';
const config = require('./config')
const amqp = require('amqplib').connect('amqp://' + config.username + ':' + config.password + '#ali3')
const co = require('co')
const args = process.argv.slice(2)
if (args.length == 0) {
console.log("Usage: rpc_client.js num");
process.exit(1)
}
function generateUuid() {
return Math.random().toString() +
Math.random().toString() +
Math.random().toString()
}
function* init(){
let conn = yield amqp
let ch = yield conn.createChannel()
let cbQueue = yield ch.assertQueue('', {exclusive: true})
return {"conn": conn, "channel": ch, "cbQueue": cbQueue}
}
function* sender(initConfig, msg, resHandler) {
try {
let ch = initConfig.channel
let conn = initConfig.conn
let cbQueue = initConfig.cbQueue
const corr = generateUuid()
console.log(' [x] [%s] Requesting fib(%d)',corr, msg)
ch.consume(cbQueue.queue, (resultMsg) => {
resHandler(resultMsg, corr, conn)
})
ch.sendToQueue('rpc_queue', new Buffer(msg.toString()), {"correlationId": corr, "replyTo": cbQueue.queue})
}
catch (ex) {
console.warn("ex:", ex)
}
}
function responseHandler(res, corr, conn) {
console.log("corr: %s - %s", corr, res.content.toString());//important debug info
if (res.properties.correlationId == corr)
{
console.log(' [.] Got %s', res.content.toString());
//setTimeout( () => {
// conn.close()
// process.exit(0)
//}, 500);
}
};
function onerror(err) {
console.error(err.stack);
}
co(function*() {
let num = parseInt(args[0])
let initConfig = yield init();
//let initConfig2 = yield init();
yield [
sender(initConfig, num.toString(), responseHandler),
sender(initConfig, (num+3).toString(), responseHandler)
]
}, onerror)
dengwei#RMBAP:~/projects/github/rabbitmq-demo/rpc$ node rpc_client_gen.js 5
[x] [0.64227353665046390.20330130192451180.5467283953912556] Requesting fib(5)
[x] [0.461023105075582860.22911424539051950.9930733679793775] Requesting fib(8)
corr: 0.64227353665046390.20330130192451180.5467283953912556 - 5
[.] Got 5
corr: 0.461023105075582860.22911424539051950.9930733679793775 - 21
[.] Got 21
from the important debug here we can see:
message has been sent from server to callback queue , and consume by client, check the uuid is set to the first message. so :
if (res.properties.correlationId == corr)
this line of code ignore the result.
seems that we can only init each time before send a single message.

Setting proxy with Alamofire Session

I am attempting to bind a proxy to a particular http request. So I have created a proxy class as outlined below which grabs a proxy and binds it using the kCF properties.
class Proxy {
func configureProxy(proxy: String) -> NSURLSessionConfiguration {
let proxyArr = proxy.componentsSeparatedByString(":")
let host = proxyArr[0]
let port = proxyArr[1].toInt()
if let p = port{
return newSession(host, port: p)
}
return NSURLSessionConfiguration.defaultSessionConfiguration()
}
func newSession(host: String, port: Int) -> NSURLSessionConfiguration {
let proxyInfo = [
kCFStreamPropertyHTTPProxyHost : host as NSString,
kCFStreamPropertyHTTPProxyPort : port,
kCFNetworkProxiesHTTPEnable as NSString : true
]
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
config.connectionProxyDictionary = proxyInfo
return config
}
}
This is my service class which receives a request from any service class which extends it. E.G (HttpBin : Service). Once all setup is done including the correct router (which is just a mutableUrlRequest), the service class is responsible for sending the http request. For testing purposes the configuration is done during the initialization of the Service class.
class Service {
var res = ResponseHandler()
var alamofireManager : Alamofire.Manager?
init(){
let ipconfig = Proxy()
let config = ipconfig.configureProxy(VALIDPROXY)
alamofireManager = Alamofire.Manager(configuration: config)
}
func createRequest(Router : routes, type : String, completion:(response: ResponseHandler) -> Void){
if let manager = alamofireManager {
println(manager.session.configuration.connectionProxyDictionary)
switch(type){
case "download":
manager.request(Router).responseImage() {
(_, _, image, error) in
self.res.image = image
self.res.success = true
completion(response: self.res)
}
break;
case "upload":
manager.upload(Router, data: uploadData)
.responseJSON { (request,response, JSON, error) in
self.res = self.getResponse(JSON, error : error)
}.responseString{ (_,_,string,error) in
if (error != nil){
println(error)
}
self.res.responseString = string
completion(response: self.res)
}
default:
manager.request(Router)
.responseJSON { (request,response, JSON, error) in
self.res = self.getResponse(JSON, error : error)
}.responseString{ (_,_,string,error) in
self.res.responseString = string
println(error)
println(string)
completion(response: self.res)
}
}
}
}
func getResponse(serverData : AnyObject?, error: NSError?)-> ResponseHandler{
if let data: AnyObject = serverData {
self.res.response = SwiftyJSON.JSON(data.0)
if(error == nil){
self.res.success = true
}
else{
self.res.error = error
println(error)
}
}
return self.res
}
}
Upon hitting my test api, the client ip from the request appears to be my ip & not the one created from the proxyInfo dictionary. manager.session.configuration.connectionProxyDictionary prints the values set in the dictionary. Any ideas if something in the Alamofire framework prevents this or if it is my implementation that is wrong?
EDIT:
I am trying some stuff by implementing NSURLProtocol and using the CFReadStreamSetProperty functions to intercept requests before they are sent. So far no luck.
Edit1:
:(

Resources