Summary
In a Raft HA cluster, write queries (CREATE, MERGE, etc.) sent over the Bolt protocol succeed only when the load balancer happens to route the connection to the current leader pod. When the connection lands on a follower, the client receives:
{neo4j_code: Neo.DatabaseError.General.UnknownError}
{message: Cannot forward command to leader: no authenticated user in the current security context}
The REST/HTTP endpoint is unaffected. Reported against an EKS deployment using the Helm chart.
Root cause
The Bolt connection handler never binds the authenticated user onto the thread-local DatabaseContext.
BoltNetworkExecutor.ensureDatabase() resolves the database via server.getDatabase(targetName) and returns it, but never calls DatabaseContext.INSTANCE.init(...).setCurrentUser(...). The Bolt module has zero references to DatabaseContext / setCurrentUser (verified across bolt/src/main/java).
On a write to a follower, RaftReplicatedDatabase.command() forwards the statement to the leader via forwardCommandToLeaderViaRaft(...), which reads the current user to set the X-ArcadeDB-Forwarded-User header:
// ha-raft/.../RaftReplicatedDatabase.java:1523
final String proxiedUser = proxied.getCurrentUserName();
if (proxiedUser == null)
throw new SecurityException(
"Cannot forward command to leader: no authenticated user in the current security context"); // :1526
builder.header("X-ArcadeDB-Forwarded-User", proxiedUser); // :1527
Because the current user was never bound to the context, getCurrentUserName() returns null and the forward throws. The Bolt driver surfaces it as Neo.DatabaseError.General.UnknownError.
Why REST and Postgres work but Bolt does not
- HTTP:
DatabaseAbstractHandler binds the user on the DatabaseContext before executing.
- Postgres:
PostgresNetworkExecutor.openDatabase() does DatabaseContext.INSTANCE.init((DatabaseInternal) database).setCurrentUser(dbUser.getDatabaseUser(database)).
- gRPC: previously had the identical bug; fixed by mirroring the Postgres pattern (regression test
GrpcFollowerForwardingIT). The fix recorded explicit guidance that any new wire-protocol module (Bolt, MongoDB, Redis) must set the current user on DatabaseContext after init. Bolt (and, as noted below, MongoDB and Redis) never received that treatment.
Affected components
bolt/src/main/java/com/arcadedb/bolt/BoltNetworkExecutor.java - ensureDatabase() does not bind the authenticated ServerSecurityUser user (already held as a field) onto the DatabaseContext.
- Likely the same defect (no
DatabaseContext/setCurrentUser binding) in mongodbw and redisw - to be audited as part of the fix. These use different write paths, so whether they hit the same .command() forwarding path needs investigation; follow-up issues may be filed if their failure mode differs.
Proposed fix
Mirror the Postgres/gRPC pattern in BoltNetworkExecutor.ensureDatabase(): after resolving / switching the database, bind the already-authenticated user:
DatabaseContext.INSTANCE.init((DatabaseInternal) database).setCurrentUser(user.getDatabaseUser(database));
This must run whenever the database is opened or switched (the ensureDatabase() switch path), and the Bolt executor runs on a dedicated per-connection thread, so the thread-local binding persists for the connection lifetime.
Steps to reproduce
- Start a 3-node ArcadeDB Raft HA cluster.
- Connect a Neo4j/Bolt driver to a follower node.
- Run a write query, e.g.
CREATE (n:Person {name:'x'}).
- Observe the
Neo.DatabaseError.General.UnknownError "no authenticated user in the current security context" failure. The same query against the leader succeeds.
Proposed regression coverage
- Unit test asserting
ensureDatabase() binds the current user on DatabaseContext.
BoltFollowerForwardingIT mirroring GrpcFollowerForwardingIT: 3-node Raft cluster (BaseRaftHATest), Neo4j driver writing to a follower, asserting the write succeeds and replicates to all nodes. (Requires adding arcadedb-ha-raft test-scoped deps to bolt/pom.xml, as grpcw/pom.xml already does.)
Summary
In a Raft HA cluster, write queries (
CREATE,MERGE, etc.) sent over the Bolt protocol succeed only when the load balancer happens to route the connection to the current leader pod. When the connection lands on a follower, the client receives:The REST/HTTP endpoint is unaffected. Reported against an EKS deployment using the Helm chart.
Root cause
The Bolt connection handler never binds the authenticated user onto the thread-local
DatabaseContext.BoltNetworkExecutor.ensureDatabase()resolves the database viaserver.getDatabase(targetName)and returns it, but never callsDatabaseContext.INSTANCE.init(...).setCurrentUser(...). The Bolt module has zero references toDatabaseContext/setCurrentUser(verified acrossbolt/src/main/java).On a write to a follower,
RaftReplicatedDatabase.command()forwards the statement to the leader viaforwardCommandToLeaderViaRaft(...), which reads the current user to set theX-ArcadeDB-Forwarded-Userheader:Because the current user was never bound to the context,
getCurrentUserName()returnsnulland the forward throws. The Bolt driver surfaces it asNeo.DatabaseError.General.UnknownError.Why REST and Postgres work but Bolt does not
DatabaseAbstractHandlerbinds the user on theDatabaseContextbefore executing.PostgresNetworkExecutor.openDatabase()doesDatabaseContext.INSTANCE.init((DatabaseInternal) database).setCurrentUser(dbUser.getDatabaseUser(database)).GrpcFollowerForwardingIT). The fix recorded explicit guidance that any new wire-protocol module (Bolt, MongoDB, Redis) must set the current user onDatabaseContextafter init. Bolt (and, as noted below, MongoDB and Redis) never received that treatment.Affected components
bolt/src/main/java/com/arcadedb/bolt/BoltNetworkExecutor.java-ensureDatabase()does not bind the authenticatedServerSecurityUser user(already held as a field) onto theDatabaseContext.DatabaseContext/setCurrentUserbinding) inmongodbwandredisw- to be audited as part of the fix. These use different write paths, so whether they hit the same.command()forwarding path needs investigation; follow-up issues may be filed if their failure mode differs.Proposed fix
Mirror the Postgres/gRPC pattern in
BoltNetworkExecutor.ensureDatabase(): after resolving / switching the database, bind the already-authenticated user:This must run whenever the database is opened or switched (the
ensureDatabase()switch path), and the Bolt executor runs on a dedicated per-connection thread, so the thread-local binding persists for the connection lifetime.Steps to reproduce
CREATE (n:Person {name:'x'}).Neo.DatabaseError.General.UnknownError"no authenticated user in the current security context" failure. The same query against the leader succeeds.Proposed regression coverage
ensureDatabase()binds the current user onDatabaseContext.BoltFollowerForwardingITmirroringGrpcFollowerForwardingIT: 3-node Raft cluster (BaseRaftHATest), Neo4j driver writing to a follower, asserting the write succeeds and replicates to all nodes. (Requires addingarcadedb-ha-rafttest-scoped deps tobolt/pom.xml, asgrpcw/pom.xmlalready does.)