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

Add address: Address to ReducerContext #299

Merged
merged 24 commits into from
Sep 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
f9abfcd
Add `address: Address` to `ReducerContext`
gefjon Sep 14, 2023
21819de
Use `None` in `ReducerContext` for HTTP calls without client address
gefjon Sep 15, 2023
7101e39
Fix typo in trait argument name
gefjon Sep 15, 2023
57a7d4d
`Address` representation amenable to SDK usage
gefjon Sep 15, 2023
4157250
Add client_address parameter to /publish; pass to init and update red…
gefjon Sep 18, 2023
a12eac6
`Address` support in Rust client SDK
gefjon Sep 20, 2023
80df5e8
Add test that addresses are stable within a client process
gefjon Sep 20, 2023
cabf1bd
Merge branch 'master' into phoebe/client-address
gefjon Sep 21, 2023
55a2e48
Run rustfmt against merge changes
gefjon Sep 21, 2023
795daaf
Not sure why my local `cargo fmt` didn't get this...
gefjon Sep 21, 2023
cca5c63
Add caller_address argument to reducer arguments in Rust SDK
gefjon Sep 22, 2023
e08ac38
rustfmt again...
gefjon Sep 22, 2023
94c77d6
Add caller address to `EventJson` message
gefjon Sep 25, 2023
7864f2c
Merge branch 'master' into phoebe/client-address
gefjon Sep 25, 2023
06cd763
Python codegen for client addresses
gefjon Sep 26, 2023
9f9fd14
Merge remote-tracking branch 'origin/master' into phoebe/client-address
gefjon Sep 26, 2023
3ca49f0
C# module support for client addresses
gefjon Sep 26, 2023
8e5a456
Fix scoping error
RReverser Sep 26, 2023
580bb26
Add `Address`-related stuff to C# codegen
gefjon Sep 27, 2023
3029561
TypeScript codegen changes
gefjon Sep 27, 2023
1410c70
Merge branch 'master' into phoebe/client-address
gefjon Sep 27, 2023
ae4d9c5
Fix merge conflict with new test
gefjon Sep 27, 2023
6a6b701
Merge branch 'master' into phoebe/client-address
gefjon Sep 29, 2023
8dc7d70
Run rustfmt
gefjon Sep 30, 2023
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
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion crates/bindings-csharp/Runtime/Module.cs
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ private static byte[] DescribeModule()
private static string? CallReducer(
uint id,
byte[] sender_identity,
byte[] sender_address,
ulong timestamp,
byte[] args
)
Expand All @@ -197,7 +198,7 @@ byte[] args
{
using var stream = new MemoryStream(args);
using var reader = new BinaryReader(stream);
reducers[(int)id].Invoke(reader, new(sender_identity, timestamp));
reducers[(int)id].Invoke(reader, new(sender_identity, sender_address, timestamp));
if (stream.Position != stream.Length)
{
throw new Exception("Unrecognised extra bytes in the reducer arguments");
Expand Down
57 changes: 52 additions & 5 deletions crates/bindings-csharp/Runtime/Runtime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -216,14 +216,61 @@ public override int GetHashCode() =>
public static SpacetimeDB.SATS.TypeInfo<Identity> GetSatsTypeInfo() => satsTypeInfo;
}

public struct Address : IEquatable<Address>
{
private readonly byte[] bytes;

public Address(byte[] bytes) => this.bytes = bytes;

public static readonly Address Zero = new(new byte[16]);

public bool Equals(Address other) =>
StructuralComparisons.StructuralEqualityComparer.Equals(bytes, other.bytes);

public override bool Equals(object? obj) => obj is Address other && Equals(other);

public static bool operator ==(Address left, Address right) => left.Equals(right);

public static bool operator !=(Address left, Address right) => !left.Equals(right);

public override int GetHashCode() =>
StructuralComparisons.StructuralEqualityComparer.GetHashCode(bytes);

public override string ToString() => BitConverter.ToString(bytes);

private static SpacetimeDB.SATS.TypeInfo<Address> satsTypeInfo =
new(
// We need to set type info to inlined address type as `generate` CLI currently can't recognise type references for built-ins.
new SpacetimeDB.SATS.ProductType
{
{ "__address_bytes", SpacetimeDB.SATS.BuiltinType.BytesTypeInfo.AlgebraicType }
},
// Concern: We use this "packed" representation (as Bytes)
// in the caller_id field of reducer arguments,
// but in table rows,
// we send the "unpacked" representation as a product value.
// It's possible that these happen to be identical
// because BSATN is minimally self-describing,
// but that doesn't seem like something we should count on.
reader => new(SpacetimeDB.SATS.BuiltinType.BytesTypeInfo.Read(reader)),
(writer, value) =>
SpacetimeDB.SATS.BuiltinType.BytesTypeInfo.Write(writer, value.bytes)
);

public static SpacetimeDB.SATS.TypeInfo<Address> GetSatsTypeInfo() => satsTypeInfo;
}

public class DbEventArgs : EventArgs
{
public readonly Identity Sender;
public readonly DateTimeOffset Time;
public readonly Address? Address;

public DbEventArgs(byte[] senderIdentity, ulong timestamp_us)
public DbEventArgs(byte[] senderIdentity, byte[] senderAddress, ulong timestamp_us)
{
Sender = new Identity(senderIdentity);
var addr = new Address(senderAddress);
Address = addr == Runtime.Address.Zero ? null : addr;
// timestamp is in microseconds; the easiest way to convert those w/o losing precision is to get Unix origin and add ticks which are 0.1ms each.
Time = DateTimeOffset.UnixEpoch.AddTicks(10 * (long)timestamp_us);
}
Expand All @@ -233,11 +280,11 @@ public DbEventArgs(byte[] senderIdentity, ulong timestamp_us)
public static event Action<DbEventArgs>? OnDisconnect;

// Note: this is accessed by C bindings.
private static string? IdentityConnected(byte[] sender_identity, ulong timestamp)
private static string? IdentityConnected(byte[] sender_identity, byte[] sender_address, ulong timestamp)
{
try
{
OnConnect?.Invoke(new(sender_identity, timestamp));
OnConnect?.Invoke(new(sender_identity, sender_address, timestamp));
return null;
}
catch (Exception e)
Expand All @@ -247,11 +294,11 @@ public DbEventArgs(byte[] senderIdentity, ulong timestamp_us)
}

// Note: this is accessed by C bindings.
private static string? IdentityDisconnected(byte[] sender_identity, ulong timestamp)
private static string? IdentityDisconnected(byte[] sender_identity, byte[] sender_address, ulong timestamp)
{
try
{
OnDisconnect?.Invoke(new(sender_identity, timestamp));
OnDisconnect?.Invoke(new(sender_identity, sender_address, timestamp));
return null;
}
catch (Exception e)
Expand Down
22 changes: 13 additions & 9 deletions crates/bindings-csharp/Runtime/bindings.c
Original file line number Diff line number Diff line change
Expand Up @@ -434,33 +434,37 @@ static Buffer return_result_buf(MonoObject* str) {

__attribute__((export_name("__call_reducer__"))) Buffer __call_reducer__(
uint32_t id,
Buffer sender_,
Buffer sender_id_,
Buffer sender_address_,
uint64_t timestamp,
Buffer args_) {
MonoArray* sender = stdb_buffer_consume(sender_);
MonoArray* sender_id = stdb_buffer_consume(sender_id_);
MonoArray* sender_address = stdb_buffer_consume(sender_address_);
MonoArray* args = stdb_buffer_consume(args_);

return return_result_buf(INVOKE_DOTNET_METHOD(
"SpacetimeDB.Runtime.dll", "SpacetimeDB.Module", "FFI", "CallReducer",
NULL, &id, sender, &timestamp, args));
NULL, &id, sender_id, sender_address, &timestamp, args));
}

__attribute__((export_name("__identity_connected__"))) Buffer
__identity_connected__(Buffer sender_, uint64_t timestamp) {
MonoArray* sender = stdb_buffer_consume(sender_);
__identity_connected__(Buffer sender_id_, Buffer sender_address_, uint64_t timestamp) {
MonoArray* sender_id = stdb_buffer_consume(sender_id_);
MonoArray* sender_address = stdb_buffer_consume(sender_address_);

return return_result_buf(
INVOKE_DOTNET_METHOD("SpacetimeDB.Runtime.dll", "SpacetimeDB", "Runtime",
"IdentityConnected", NULL, sender, &timestamp));
"IdentityConnected", NULL, sender_id, sender_address, &timestamp));
}

__attribute__((export_name("__identity_disconnected__"))) Buffer
__identity_disconnected__(Buffer sender_, uint64_t timestamp) {
MonoArray* sender = stdb_buffer_consume(sender_);
__identity_disconnected__(Buffer sender_id_, Buffer sender_address_, uint64_t timestamp) {
MonoArray* sender_id = stdb_buffer_consume(sender_id_);
MonoArray* sender_address = stdb_buffer_consume(sender_address_);

return return_result_buf(
INVOKE_DOTNET_METHOD("SpacetimeDB.Runtime.dll", "SpacetimeDB", "Runtime",
"IdentityDisconnected", NULL, sender, &timestamp));
"IdentityDisconnected", NULL, sender_id, sender_address, &timestamp));
}

// Shims to avoid dependency on WASI in the generated Wasm file.
Expand Down
25 changes: 21 additions & 4 deletions crates/bindings-macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -368,10 +368,23 @@ fn gen_reducer(original_function: ItemFn, reducer_name: &str, extra: ReducerExtr
};

let generated_function = quote! {
fn __reducer(__sender: spacetimedb::sys::Buffer, __timestamp: u64, __args: &[u8]) -> spacetimedb::sys::Buffer {
// NOTE: double-underscoring names here is unnecessary, as Rust macros are hygienic.
fn __reducer(
__sender: spacetimedb::sys::Buffer,
__caller_address: spacetimedb::sys::Buffer,
__timestamp: u64,
__args: &[u8]
) -> spacetimedb::sys::Buffer {
#(spacetimedb::rt::assert_reducerarg::<#arg_tys>();)*
#(spacetimedb::rt::assert_reducerret::<#ret_ty>();)*
spacetimedb::rt::invoke_reducer(#func_name, __sender, __timestamp, __args, |_res| { #epilogue })
spacetimedb::rt::invoke_reducer(
#func_name,
__sender,
__caller_address,
__timestamp,
__args,
|_res| { #epilogue },
)
}
};

Expand Down Expand Up @@ -889,8 +902,12 @@ fn spacetimedb_connect_disconnect(item: TokenStream, connect: bool) -> syn::Resu
let emission = quote! {
const _: () = {
#[export_name = #connect_disconnect_symbol]
extern "C" fn __connect_disconnect(__sender: spacetimedb::sys::Buffer, __timestamp: u64) -> spacetimedb::sys::Buffer {
spacetimedb::rt::invoke_connection_func(#func_name, __sender, __timestamp)
extern "C" fn __connect_disconnect(
__sender: spacetimedb::sys::Buffer,
__caller_address: spacetimedb::sys::Buffer,
__timestamp: u64,
) -> spacetimedb::sys::Buffer {
spacetimedb::rt::invoke_connection_func(#func_name, __sender, __caller_address, __timestamp)
}
};

Expand Down
10 changes: 10 additions & 0 deletions crates/bindings/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ pub use spacetimedb_bindings_macro::{duration, query, spacetimedb, TableType};
pub use sats::SpacetimeType;
pub use spacetimedb_lib;
pub use spacetimedb_lib::sats;
pub use spacetimedb_lib::Address;
pub use spacetimedb_lib::AlgebraicValue;
pub use spacetimedb_lib::Identity;
pub use timestamp::Timestamp;
Expand Down Expand Up @@ -51,6 +52,14 @@ pub struct ReducerContext {
pub sender: Identity,
/// The time at which the reducer was started.
pub timestamp: Timestamp,
/// The `Address` of the client that invoked the reducer.
///
/// `None` if no `Address` was supplied to the `/database/call` HTTP endpoint,
/// or via the CLI's `spacetime call` subcommand.
///
/// For automatic reducers, i.e. `init`, `update` and scheduled reducers,
/// this will be the module's `Address`.
pub address: Option<Address>,
}

impl ReducerContext {
Expand All @@ -59,6 +68,7 @@ impl ReducerContext {
Self {
sender: Identity::__dummy(),
timestamp: Timestamp::UNIX_EPOCH,
address: None,
}
}
}
Expand Down
46 changes: 37 additions & 9 deletions crates/bindings/src/rt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use spacetimedb_lib::de::{self, Deserialize, SeqProductAccess};
use spacetimedb_lib::sats::typespace::TypespaceBuilder;
use spacetimedb_lib::sats::{impl_deserialize, impl_serialize, AlgebraicType, AlgebraicTypeRef, ProductTypeElement};
use spacetimedb_lib::ser::{Serialize, SerializeSeqProduct};
use spacetimedb_lib::{bsatn, Identity, MiscModuleExport, ModuleDef, ReducerDef, TableDef, TypeAlias};
use spacetimedb_lib::{bsatn, Address, Identity, MiscModuleExport, ModuleDef, ReducerDef, TableDef, TypeAlias};
use sys::Buffer;

pub use once_cell::sync::{Lazy, OnceCell};
Expand All @@ -28,11 +28,12 @@ pub use once_cell::sync::{Lazy, OnceCell};
pub fn invoke_reducer<'a, A: Args<'a>, T>(
reducer: impl Reducer<'a, A, T>,
sender: Buffer,
client_address: Buffer,
timestamp: u64,
args: &'a [u8],
epilogue: impl FnOnce(Result<(), &str>),
) -> Buffer {
let ctx = assemble_context(sender, timestamp);
let ctx = assemble_context(sender, timestamp, client_address);

// Deserialize the arguments from a bsatn encoding.
let SerDeArgs(args) = bsatn::from_slice(args).expect("unable to decode args");
Expand Down Expand Up @@ -71,21 +72,42 @@ pub fn create_index(index_name: &str, table_id: u32, index_type: sys::raw::Index
pub fn invoke_connection_func<R: ReducerResult>(
f: impl Fn(ReducerContext) -> R,
sender: Buffer,
client_address: Buffer,
timestamp: u64,
) -> Buffer {
let ctx = assemble_context(sender, timestamp);
let ctx = assemble_context(sender, timestamp, client_address);

let res = with_timestamp_set(ctx.timestamp, || f(ctx).into_result());
cvt_result(res)
}

/// Creates a reducer context from the given `sender` and `timestamp`.
fn assemble_context(sender: Buffer, timestamp: u64) -> ReducerContext {
/// Creates a reducer context from the given `sender`, `timestamp` and `client_address`.
///
/// `sender` must contain 32 bytes, from which we will read an `Identity`.
///
/// `timestamp` is a count of microseconds since the Unix epoch.
///
/// `client_address` must contain 16 bytes, from which we will read an `Address`.
/// The all-zeros `client_address` (constructed by [`Address::__dummy`]) is used as a sentinel,
/// and translated to `None`.
fn assemble_context(sender: Buffer, timestamp: u64, client_address: Buffer) -> ReducerContext {
let sender = Identity::from_byte_array(sender.read_array::<32>());

let timestamp = Timestamp::UNIX_EPOCH + Duration::from_micros(timestamp);

ReducerContext { sender, timestamp }
let address = Address::from_arr(&client_address.read_array::<16>());

let address = if address == Address::__dummy() {
None
} else {
Some(address)
};

ReducerContext {
sender,
timestamp,
address,
}
}

/// Converts `errno` into a string message.
Expand Down Expand Up @@ -471,7 +493,7 @@ impl TypespaceBuilder for ModuleBuilder {
static DESCRIBERS: Mutex<Vec<fn(&mut ModuleBuilder)>> = Mutex::new(Vec::new());

/// A reducer function takes in `(Sender, Timestamp, Args)` and writes to a new `Buffer`.
pub type ReducerFn = fn(Buffer, u64, &[u8]) -> Buffer;
pub type ReducerFn = fn(Buffer, Buffer, u64, &[u8]) -> Buffer;
static REDUCERS: OnceCell<Vec<ReducerFn>> = OnceCell::new();

/// Describes the module into a serialized form that is returned and writes the set of `REDUCERS`.
Expand All @@ -497,8 +519,14 @@ extern "C" fn __describe_module__() -> Buffer {
///
/// The result of the reducer is written into a fresh buffer.
#[no_mangle]
extern "C" fn __call_reducer__(id: usize, sender: Buffer, timestamp: u64, args: Buffer) -> Buffer {
extern "C" fn __call_reducer__(
id: usize,
sender: Buffer,
caller_address: Buffer,
timestamp: u64,
args: Buffer,
) -> Buffer {
let reducers = REDUCERS.get().unwrap();
let args = args.read();
reducers[id](sender, timestamp, &args)
reducers[id](sender, caller_address, timestamp, &args)
}
Loading
Loading