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

Update expensify_prod branch #2019

Merged
merged 7 commits into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions sqlitecluster/SQLite.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1012,14 +1012,21 @@ int SQLite::_authorize(int actionCode, const char* detail1, const char* detail2,
!strcmp(detail2, "strftime") ||
!strcmp(detail2, "changes") ||
!strcmp(detail2, "last_insert_rowid") ||
!strcmp(detail2, "sqlite3_version")
!strcmp(detail2, "sqlite_version")
) {
_isDeterministicQuery = false;
}

if (!strcmp(detail2, "current_timestamp")) {
// Prevent using certain non-deterministic functions in writes which could cause synchronization with followers to
// result in inconsistent data. Some are not included here because they can be used in a deterministic way that is valid.
// i.e. you can do UPDATE x = DATE('2024-01-01') and its deterministic whereas UPDATE x = DATE('now') is not. It's up to
// callers to prevent using these functions inappropriately.
if (!strcmp(detail2, "current_timestamp") ||
!strcmp(detail2, "random") ||
!strcmp(detail2, "last_insert_rowid") ||
!strcmp(detail2, "changes") ||
!strcmp(detail2, "sqlite_version")) {
if (_currentlyWriting) {
// Prevent using `current_timestamp` in writes which could cause synchronization with followers to result in inconsistent data.
return SQLITE_DENY;
}
}
Expand Down
9 changes: 9 additions & 0 deletions sqlitecluster/SQLite.h
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,15 @@ class SQLite {
void _checkInterruptErrors(const string& error) const;

// Called internally by _sqliteAuthorizerCallback to authorize columns for a query.
//
// PRO-TIP: you can play with the authorizer using the `sqlite3` CLI tool, by running `.auth ON` then running
// your query. The columns displayed are the same as what is passed to this function.
//
// The information passed to this function is different based on the first parameter, actionCode.
// You can see what information is passed for each action code here https://www.sqlite.org/c3ref/c_alter_table.html.
// Note that as of writing this comment, the page seems slightly out of date and the parameter numbers are all off
// by one. That is, the first paramter passed to the callback funciton is actually the integer action code, not the
// second.
int _authorize(int actionCode, const char* detail1, const char* detail2, const char* detail3, const char* detail4);

// It's possible for certain transactions (namely, timing out a write operation, see here:
Expand Down
31 changes: 9 additions & 22 deletions sqlitecluster/SQLiteNode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,20 +56,6 @@
// dbCountAtStart: The highest committed transaction in the DB at the start of this transaction on leader, for
// optimizing replication.

// NOTE: This comment as well as NODE_LOGIN should be removed after https://github.com/Expensify/Bedrock/pull/1999 is deployed.
// On LOGIN vs NODE_LOGIN.
// _onConnect sends a LOGIN message.
// _onConnect is called in exctly two places:
// 1. In response to a NODE_LOGIN message received on a newly connected socket on the sync port. It's expected when
// establishing a connection, a node sends this NODE_LOGIN as its first message.
// 2. Immediately following establishing a TCP connection to another node and sending a NODE_LOGIN message. In the case that
// we are the initiating node, we immediately queue three messages:
// 1. NODE_LOGIN
// 2. PING
// 3. LOGIN
//
// When we receive a NODE_LOGIN, we immediately respond with a PING followed by a LOGIN (by calling _onConnect).

#undef SLOGPREFIX
#define SLOGPREFIX "{" << _name << "/" << SQLiteNode::stateName(_state) << "} "

Expand Down Expand Up @@ -1269,7 +1255,10 @@ void SQLiteNode::_onMESSAGE(SQLitePeer* peer, const SData& message) {
SINFO("Received PONG from peer '" << peer->name << "' (" << peer->latency/1000 << "ms latency)");
return;
} else if (SIEquals(message.methodLine, "NODE_LOGIN")) {
// Do nothing, this keeps this code from warning until NODE_LOGIN is deprecated.
// We need to return early here to ignore this deprecated message and avoid throwing:
// STHROW("not logged in");
// Below. We can remove this check after one more deploy cycle.
// https://github.com/Expensify/Expensify/issues/450953
return;
}

Expand Down Expand Up @@ -2553,7 +2542,7 @@ void SQLiteNode::postPoll(fd_map& fdm, uint64_t& nextActivity) {
// with peers, so we store any that we can remove in this list.
list<Socket*> socketsToRemove;

// Check each new connection for a NODE_LOGIN message.
// Check each new connection for a LOGIN message.
for (auto socket : _unauthenticatedIncomingSockets) {
STCPManager::postPoll(fdm, *socket);
try {
Expand All @@ -2565,7 +2554,9 @@ void SQLiteNode::postPoll(fd_map& fdm, uint64_t& nextActivity) {
int messageSize = message.deserialize(socket->recvBuffer);
if (messageSize) {
socket->recvBuffer.consumeFront(messageSize);
// Allow either LOGIN or NODE_LOGIN until we deprecate NODE_LOGIN.
// Old nodes, for one more upgrade cycle, will still send `NODE_LOGIN`. We can remove this check after this
// code is deployed.
// See: https://github.com/Expensify/Expensify/issues/450953
if (SIEquals(message.methodLine, "NODE_LOGIN") || SIEquals(message.methodLine, "LOGIN")) {
SQLitePeer* peer = getPeerByName(message["Name"]);
if (peer) {
Expand All @@ -2591,7 +2582,7 @@ void SQLiteNode::postPoll(fd_map& fdm, uint64_t& nextActivity) {
STHROW("Unauthenticated node '" + message["Name"] + "' attempted to connected, rejecting.");
}
} else {
STHROW("expecting LOGIN or NODE_LOGIN");
STHROW("expecting LOGIN");
}
} else if (STimeNow() > socket->lastRecvTime + 5'000'000) {
STHROW("Incoming socket didn't send a message for over 5s, closing.");
Expand All @@ -2614,10 +2605,6 @@ void SQLiteNode::postPoll(fd_map& fdm, uint64_t& nextActivity) {
switch (result) {
case SQLitePeer::PeerPostPollStatus::JUST_CONNECTED:
{
// When NODE_LOGIN is deprecated, we can remove the next 3 lines.
SData login("NODE_LOGIN");
login["Name"] = _name;
_sendToPeer(peer, login);
_onConnect(peer);
_sendPING(peer);
}
Expand Down
4 changes: 2 additions & 2 deletions sqlitecluster/SQLiteNode.h
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ class SQLiteNode : public STCPManager {
// would be a good idea for the caller to read any new commands or traffic from the network.
bool update();

// Look up the correct peer by the name it supplies in a NODE_LOGIN
// Look up the correct peer by the name it supplies in a LOGIN
// message. Does not lock, but this method is const and all it does is
// access _peerList and peer->name, both of which are const. So it is safe
// to call from other public functions.
Expand Down Expand Up @@ -293,7 +293,7 @@ class SQLiteNode : public STCPManager {
const string _version;

// These are sockets that have been accepted on the node port but have not yet been associated with a peer (because
// they need to send a NODE_LOGIN message with their name first).
// they need to send a LOGIN message with their name first).
set<Socket*> _unauthenticatedIncomingSockets;

// The write consistency requested for the current in-progress commit.
Expand Down
20 changes: 17 additions & 3 deletions test/tests/WriteTest.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include <libstuff/SData.h>
#include <libstuff/SRandom.h>
#include <test/lib/BedrockTester.h>

struct WriteTest : tpunit::TestFixture {
Expand All @@ -18,7 +19,7 @@ struct WriteTest : tpunit::TestFixture {
TEST(WriteTest::updateAndInsertWithHttp),
TEST(WriteTest::shortHandSyntax),
TEST(WriteTest::keywordsAsValue),
TEST(WriteTest::blockTimeFunctions),
TEST(WriteTest::blockNonDeterministicFunctions),
AFTER_CLASS(WriteTest::tearDown)) { }

BedrockTester* tester;
Expand All @@ -38,7 +39,8 @@ struct WriteTest : tpunit::TestFixture {
for (int i = 0; i < 50; i++) {
SData query("Query");
query["writeConsistency"] = "ASYNC";
query["query"] = "INSERT INTO foo VALUES ( RANDOM() );";
uint64_t rand = SRandom::rand64();
query["query"] = "INSERT INTO foo VALUES (" + to_string(rand) + ");";
tester->executeWaitVerifyContent(query);
}

Expand Down Expand Up @@ -181,7 +183,7 @@ struct WriteTest : tpunit::TestFixture {
tester->executeWaitVerifyContent(query3);
}

void blockTimeFunctions() {
void blockNonDeterministicFunctions() {
// Verify writing the string 'CURRENT_TIMESTAMP' is fine.
SData query("query: INSERT INTO stuff VALUES ( NULL, 11, 'CURRENT_TIMESTAMP' );");
tester->executeWaitVerifyContent(query);
Expand All @@ -193,6 +195,18 @@ struct WriteTest : tpunit::TestFixture {
// But allow the function to run in reads.
query.methodLine = "query: SELECT CURRENT_TIMESTAMP;";
tester->executeWaitVerifyContent(query);

// Verify writing the string 'RANDOM' is fine.
query.methodLine = "query: INSERT INTO stuff VALUES ( NULL, 11, 'RANDOM' );";
tester->executeWaitVerifyContent(query);

// But verify calling the function RANDOM is blocked when writing.
query.methodLine = "query: INSERT INTO stuff VALUES ( NULL, 11, RANDOM() );";
tester->executeWaitVerifyContent(query, "502 Query failed");

// But allow the function to run in reads.
query.methodLine = "query: SELECT random();";
tester->executeWaitVerifyContent(query);
}

} __WriteTest;
Loading