-
Notifications
You must be signed in to change notification settings - Fork 120
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
nimbus-verified-proxy integration with status-go #1572
Conversation
9fdf130
to
5d7a939
Compare
@@ -40,14 +40,27 @@ func getConfiguredChainId(networkMetadata: Eth2NetworkMetadata): Quantity = | |||
else: | |||
return networkMetadata.cfg.DEPOSIT_CHAIN_ID.Quantity | |||
|
|||
proc run(config: VerifiedProxyConf) {.raises: [CatchableError].} = | |||
type OnHeaderCallback* = proc (s: cstring) {.cdecl.} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
type OnHeaderCallback* = proc (s: cstring) {.cdecl.} | |
type OnHeaderCallback* = proc (s: cstring) {.cdecl, raises: [], gcsafe.} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
echo "finalized header callback set" | ||
|
||
|
||
proc run(config: VerifiedProxyConf) {.raises: [CatchableError, Exception].} = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
proc run(config: VerifiedProxyConf) {.raises: [CatchableError, Exception].} = | |
proc run(config: VerifiedProxyConf) {.raises: [CatchableError].} = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With this change, i get the following compiler error on that line:
Error: Exception can raise an unlisted exception: Exception
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For this exception, you need to track down where the Exception
is coming from - usually, it's the callback
that I noted above should be annotated, else the compiler will assume the most broad exception raising.
You can figure it out by putting try/except around specific lines until it goes away: the proper fix is what I suggest above for the callbacks, to annotate them with raises: [], gcsafe
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, figured it out - it was getCLIParams()
that threw the exception.
notice "### Invoking finalizedHeaderCallback" | ||
{.gcsafe.}: | ||
try: | ||
finalizedHeaderCallback(Json.encode(finalizedHeader)) | ||
except Exception as e: | ||
notice "finalizedHeaderCallback exception" | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
notice "### Invoking finalizedHeaderCallback" | |
{.gcsafe.}: | |
try: | |
finalizedHeaderCallback(Json.encode(finalizedHeader)) | |
except Exception as e: | |
notice "finalizedHeaderCallback exception" | |
notice "### Invoking finalizedHeaderCallback" | |
finalizedHeaderCallback(Json.encode(finalizedHeader)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@arnetheduck thank you for review!
The Json.encode()
invocation throws SerializationError
. If not wrapped with try-except
the following compiler error is shown:
Error: writeValue writer`gensym1293, optimisticHeader can raise an unlisted exception: SerializationError
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
right, then the right approach is to specifically catch that (try: ... except SerializationError...
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it, thanks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
@@ -171,6 +192,14 @@ proc run(config: VerifiedProxyConf) {.raises: [CatchableError].} = | |||
info "New LC optimistic header", | |||
optimistic_header = shortLog(forkyHeader) | |||
optimisticProcessor.setOptimisticHeader(forkyHeader.beacon) | |||
if optimisticHeaderCallback != nil: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
349972d
to
92da4e4
Compare
@arnetheduck can you please have a look at the fixes? Hopefully this looks better now. |
@arnetheduck @zah can you please review the fixes made by @Ivansete-status ? |
echo "Quitting" | ||
|
||
proc NimMain() {.importc.} | ||
proc startProxyViaJson*(configJson: cstring) {.exportc, dynlib.} = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, we need to think about this API in particular - when called from go
, this would block the go thread until finished.
As an alternative to this model, I would propose the following API:
type Context = object
thread: Thread[ptr Context]
config: cstring
stop: bool
onFinalizedHeader: proc...
proc runContext(ctx: ptr Context) {.thread.} =
let node = newLightClientNode(ctx.config)
while not ctx[].stop and node.running:
let timeout = sleepAsync(100.millis)
waitFor timeout
# do cleanup
node.stop()
proc startLightClientProxy(config: cstring, onFinalizedHeader: ...): ptr Context =
let ctx = createShared(Context, 1)
ctx.config = cast[cstring](allocShared0(len(config) + 1))
ctx.onFinalizedHeader = ...
copyMem(ctx.config, config, len(config))
createThread(ctx.thread, runContext, ctx)
proc stopLightClientProxy(ctx: ptr Context) =
ctx[].stop = false
ctx.thread.joinThreads()
deallocShared(ctx.config)
...
freeShared(ctx)
In the above setup, the light client runs in its own thread and all "shared" memory is allocated inside the Context
object which is freed when go
wants to stop the proxy.
Instead of calling run(config)
which calls runForever
, we use a custom polling loop that wakes up every 100ms to check if we should shut down - later, we can replace this with a more sophisticated method but this is sufficient for now.
So, to rehash the key ideas:
go
calls a "start" function that takes as parameter the configuration and any callbacks that are needed- the "start" function allocates a context which is given back to
go
-go
doesn't interact with this context directly but it's used in the rest of the API- the "start" function does not block
- the node operates as usual in its own thread - the only difference is the polling for the
stop
boolean that happens every now and then - notice in particular how the
node
instance is allocated inside the thread - when it's time to shut down, go calls the "stop" function giving the context - it blocks until the node has stopped
Later, the Context
object will also hold a queue for interacting with the node from go
- this queue will be used to make sure that there are no unsanctioned memory interactions between go
and nim
- it looks like so:
proc sendRequest(ctx: ptr Context, data: ptr byte, len: cint) =
let copy = allocShared(len)
copyMem(copy, data, len)
ctx.queue.add((copy, len))
If a return value is needed, this is easiest to do via a callback:
proc sendRequest(ctx: ptr Context, data: ptr byte, len: cint, user: pointer, callback: proc(user: pointer, response: ptr byte, len: cint) {.cdecl.}) =
let copy = allocShared(len)
copyMem(copy, data, len)
ctx.queue.add((copy, len, callback))
when the result of the call is ready, the callback is simply called making it easy to integrate with the async behavior of go
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Confirming that cgo can be called from non-go threads: golang/go@6c97639
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
status-im/nim-style-guide#15 contains more examples - will be adding a go example soon as well
130f246
to
e04d676
Compare
119fdaa
to
aa5199b
Compare
let's move the code needed to compile the proxy as a library to a separate file like so:
Similar to the above two libraries, the C header file should be written manually to expose the functions - it's probably easier to create a static library instead of a dll/so and link everything statically. We can also create the PR for confutils and get that merged ASAP so that it is not a blocker. |
Tried rebasing, but this causes issues related to Nim version i think. I start getting the below errors when building
(pushed the rebased version to https://github.com/status-im/nimbus-eth1/tree/status_go_integration_rebase) |
did you upgrade your copy of the dependencies (nim-stew etc) after rebasing? |
i think i did - i made sure that all submodule deps are identical to the ones in |
The draft PR for rebased version here: #1971 |
Closing in favor of #1971 |
This is depended on by https://github.com/siphiuel/lc-proxy-wrapper.
Custom
nim-confutils
dependency is due to adding a fix suggested in status-im/nim-confutils#31.
Tracking issue: status-im/status-go#3282
Supersedes #1497