Skip to content
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

Closed
wants to merge 5 commits into from

Conversation

vitvly
Copy link
Contributor

@vitvly vitvly commented May 3, 2023

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

@vitvly vitvly force-pushed the status_go_integration branch 2 times, most recently from 9fdf130 to 5d7a939 Compare May 8, 2023 13:58
@@ -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.}
Copy link
Member

@arnetheduck arnetheduck May 8, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
type OnHeaderCallback* = proc (s: cstring) {.cdecl.}
type OnHeaderCallback* = proc (s: cstring) {.cdecl, raises: [], gcsafe.}

Copy link
Contributor Author

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].} =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
proc run(config: VerifiedProxyConf) {.raises: [CatchableError, Exception].} =
proc run(config: VerifiedProxyConf) {.raises: [CatchableError].} =

Copy link
Contributor Author

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

Copy link
Member

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

Copy link
Contributor Author

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.

Comment on lines 180 to 190
notice "### Invoking finalizedHeaderCallback"
{.gcsafe.}:
try:
finalizedHeaderCallback(Json.encode(finalizedHeader))
except Exception as e:
notice "finalizedHeaderCallback exception"

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
notice "### Invoking finalizedHeaderCallback"
{.gcsafe.}:
try:
finalizedHeaderCallback(Json.encode(finalizedHeader))
except Exception as e:
notice "finalizedHeaderCallback exception"
notice "### Invoking finalizedHeaderCallback"
finalizedHeaderCallback(Json.encode(finalizedHeader))

Copy link
Contributor Author

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

Copy link
Member

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...).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it, thanks.

Copy link
Contributor Author

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:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@vitvly vitvly force-pushed the status_go_integration branch 2 times, most recently from 349972d to 92da4e4 Compare May 15, 2023 16:14
@vitvly
Copy link
Contributor Author

vitvly commented May 18, 2023

@arnetheduck can you please have a look at the fixes? Hopefully this looks better now.

@vitvly
Copy link
Contributor Author

vitvly commented Jun 19, 2023

@arnetheduck @zah can you please review the fixes made by @Ivansete-status ?

echo "Quitting"

proc NimMain() {.importc.}
proc startProxyViaJson*(configJson: cstring) {.exportc, dynlib.} =
Copy link
Member

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

Copy link
Member

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

Copy link
Member

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

@arnetheduck
Copy link
Member

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.

@vitvly
Copy link
Contributor Author

vitvly commented Jan 15, 2024

Tried rebasing, but this causes issues related to Nim version i think. I start getting the below errors when building libverifproxy:

/Users/vvlasov/c/nimbus-eth1/nimbus/db/aristo/aristo_blobify.nim(193, 27) Error: undeclared field: 'keys' for type tables.Table [type declared in /Users/vvlasov/c/nimbus-eth1/vendor/nimbus-build-system/vendor/Nim/lib/pure/collections/tables.nim(206, 3)]

(pushed the rebased version to https://github.com/status-im/nimbus-eth1/tree/status_go_integration_rebase)

@arnetheduck
Copy link
Member

Tried rebasing, but this causes issues

did you upgrade your copy of the dependencies (nim-stew etc) after rebasing?

@vitvly
Copy link
Contributor Author

vitvly commented Jan 15, 2024

i think i did - i made sure that all submodule deps are identical to the ones in master, except for nim-confutils. Then did make mrproper, make update, make libverifproxy. Must be something that i've messed up, i guess.

@vitvly
Copy link
Contributor Author

vitvly commented Jan 15, 2024

The draft PR for rebased version here: #1971

@vitvly
Copy link
Contributor Author

vitvly commented Jan 25, 2024

Closing in favor of #1971

@vitvly vitvly closed this Jan 25, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants