Initial commit

This commit is contained in:
Sam Schetterer 2020-10-26 18:49:11 +08:00
commit 51aa60bbc6
13 changed files with 513 additions and 0 deletions

7
.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
target
.env
.vscode
bin
config.json
node_modules
./package-lock.json

4
Cargo.toml Normal file
View File

@ -0,0 +1,4 @@
[workspace]
members = [
"messaging",
]

13
LICENSE Normal file
View File

@ -0,0 +1,13 @@
Copyright 2020 Serum Foundation.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

3
README.md Normal file
View File

@ -0,0 +1,3 @@
This adds a statically-sized on-chain messaging buffer. This can be used to transmit arbitrary byte messages on-chain
STILL WIP

24
bpf-sdk-install.sh Executable file
View File

@ -0,0 +1,24 @@
#!/usr/bin/env bash
set -e
channel=${1:-v1.3.17}
installDir="$(dirname "$0")"/bin
cacheDir=~/.cache/solana-bpf-sdk/"$channel"
echo "Installing $channel BPF SDK into $installDir"
set -x
if [[ ! -r "$cacheDir"/bpf-sdk.tar.bz2 ]]; then
mkdir -p "$cacheDir"
curl -L --retry 5 --retry-delay 2 -o "$cacheDir"/bpf-sdk.tar.bz2 \
https://solana-sdk.s3.amazonaws.com/"$channel"/bpf-sdk.tar.bz2
fi
rm -rf "$installDir"
mkdir -p "$installDir"
(
cd "$installDir"
tar jxf "$cacheDir"/bpf-sdk.tar.bz2
)
cat "$installDir"/bpf-sdk/version.txt

15
cbindgen.sh Executable file
View File

@ -0,0 +1,15 @@
#!/usr/bin/env bash
cd "$(dirname "$0")"
set -x
# Cargo.lock can cause older spl-token bindings to be generated? Move it out of
# the way...
mv -f Cargo.lock Cargo.lock.org
cargo run --manifest-path=utils/cgen/Cargo.toml
exitcode=$?
mv -f Cargo.lock.org Cargo.lock
exit $exitcode

212
do.sh Executable file
View File

@ -0,0 +1,212 @@
#!/usr/bin/env bash
CALLER_PWD=$PWD
cd "$(dirname "$0")"
usage() {
cat <<EOF
Usage: do.sh <action> <project> <action specific arguments>
Supported actions:
build
build-lib
clean
clippy
doc
dump
fmt
test
update
Supported projects:
all
any directory containing a Cargo.toml file
EOF
}
sdkDir=bin/bpf-sdk
profile=bpfel-unknown-unknown/release
readCargoVariable() {
declare variable="$1"
declare Cargo_toml="$2"
while read -r name equals value _; do
if [[ $name = "$variable" && $equals = = ]]; then
echo "${value//\"/}"
return
fi
done < <(cat "$Cargo_toml")
echo "Unable to locate $variable in $Cargo_toml" 1>&2
}
perform_action() {
set -e
# Use relative path if arg starts with "."
if [[ $2 == .* ]]; then
projectDir="$CALLER_PWD"/$2
else
projectDir="$PWD"/$2
fi
targetDir="$PWD"/target
features=
crateName="$(readCargoVariable name "$projectDir/Cargo.toml")"
so_path="$targetDir/$profile"
so_name="${crateName//\-/_}"
so_name_unstripped="${so_name}_unstripped"
if [[ -f "$projectDir"/Xargo.toml ]]; then
features="--features=program"
fi
case "$1" in
build)
if [[ -f "$projectDir"/Xargo.toml ]]; then
echo "build $crateName ($projectDir)"
"$sdkDir"/rust/build.sh "$projectDir"
cp "$so_path/${so_name}.so" "$so_path/${so_name_unstripped}.so"
"$sdkDir"/dependencies/llvm-native/bin/llvm-objcopy --strip-all "$so_path/${so_name}.so" "$so_path/${so_name}.so"
else
echo "$projectDir does not contain a program, skipping"
fi
;;
build-lib)
(
cd "$projectDir"
echo "build-lib $crateName ($projectDir)"
export RUSTFLAGS="${@:3}"
cargo build
)
;;
clean)
"$sdkDir"/rust/clean.sh "$projectDir"
;;
clippy)
(
cd "$projectDir"
echo "clippy $crateName ($projectDir)"
cargo +nightly clippy $features ${@:3}
)
;;
doc)
(
cd "$projectDir"
echo "generating docs $crateName ($projectDir)"
cargo doc ${@:3}
)
;;
dump)
# Dump depends on tools that are not installed by default and must be installed manually
# - readelf
# - rustfilt
if [[ -f "$projectDir"/Xargo.toml ]]; then
if ! which rustfilt > /dev/null; then
echo "Error: rustfilt not found. It can be installed by running: cargo install rustfilt"
exit 1
fi
if ! which readelf > /dev/null; then
if [[ $(uname) = Darwin ]]; then
echo "Error: readelf not found. It can be installed by running: brew install binutils"
else
echo "Error: readelf not found."
fi
exit 1
fi
(
cd "$CALLER_PWD"
"$0" build "$2"
)
echo "dump $crateName ($projectDir)"
so="$so_path/${so_name}.so"
if [[ ! -r "$so" ]]; then
echo "Error: No dump created, cannot read $so"
exit 1
fi
dump="$so_path/${so_name}_dump"
(
set -x
ls -la "$so" > "${dump}_mangled.txt"
readelf -aW "$so" >>"${dump}_mangled.txt"
"$sdkDir/dependencies/llvm-native/bin/llvm-objdump" \
-print-imm-hex \
--source \
--disassemble \
"$so" \
>> "${dump}_mangled.txt"
sed s/://g <"${dump}_mangled.txt" | rustfilt >"${dump}.txt"
)
if [[ -f "$dump.txt" ]]; then
echo "Created $dump.txt"
else
echo "Error: No dump created"
exit 1
fi
else
echo "$projectDir does not contain a program, skipping"
fi
;;
fmt)
(
cd "$projectDir"
echo "formatting $projectDir"
cargo fmt ${@:3}
)
;;
help)
usage
exit
;;
test)
(
cd "$projectDir"
echo "test $projectDir"
cargo test $features ${@:3}
)
;;
update)
./bpf-sdk-install.sh
./do.sh clean all
;;
*)
echo "Error: Unknown command"
usage
exit
;;
esac
}
set -e
if [[ $1 == "update" ]]; then
perform_action "$1"
exit
else
if [[ "$#" -lt 2 ]]; then
usage
exit
fi
if [[ ! -d "$sdkDir" ]]; then
./do.sh update
fi
fi
if [[ $2 == "all" ]]; then
# Perform operation on all projects
for project in */program*; do
if [[ -f "$project"/Cargo.toml ]]; then
perform_action "$1" "${project%/}" ${@:3}
else
continue
fi
done
else
# Perform operation on requested project
if [[ -d $2/program ]]; then
perform_action "$1" "$2/program" "${@:3}"
else
perform_action "$1" "$2" "${@:3}"
fi
fi
exit 0

24
messaging/Cargo.toml Normal file
View File

@ -0,0 +1,24 @@
# Note: This crate must be built using do.sh
[package]
name = "spl-packet"
version = "0.1.0"
description = "Basic packet sending protocol"
license = "Apache-2.0"
edition = "2018"
[features]
no-entrypoint = []
program = ["solana-sdk/program"]
default = ["solana-sdk/default"]
[dependencies]
num-derive = "0.3"
num-traits = "0.2"
solana-sdk = { version = "1.3.17", default-features = false, optional = true }
thiserror = "1.0"
num_enum = "0.5.1"
[lib]
crate-type = ["cdylib", "lib"]

2
messaging/Xargo.toml Normal file
View File

@ -0,0 +1,2 @@
[target.bpfel-unknown-unknown.dependencies.std]
features = []

107
messaging/src/buffer.rs Normal file
View File

@ -0,0 +1,107 @@
//! Super basic messaging buffer with limited history
use crate::error::MessageError;
use solana_sdk::{program_error::ProgramError, pubkey::Pubkey};
const MESSAGE_LENGTH: usize = 256;
const MESSAGE_HISTORY: usize = 128;
/**
* Basic on-chain message struct - defines layout of the message
*/
#[repr(packed)]
#[derive(Copy, Clone)]
pub struct Message {
/// Key of the writer of the transaction
pub writer: Pubkey,
/// Number of bytes contained in the message
pub length: u8,
/// Actual instruction data
pub bytes: [u8; MESSAGE_LENGTH],
}
/**
* Represent the on-chain state of a message buffer
*/
pub struct MessageBuffer {
/// Index of the first message in the buffer
pub queue_head: u32,
/// Array containing messages
pub messages: [Message; MESSAGE_HISTORY],
}
impl MessageBuffer {
/// Appends a message to the queue, possibly overwriting an old one
#[inline]
pub fn append(&mut self, message: &Message) {
let next_head = self.queue_head + 1;
let next_head = next_head % MESSAGE_HISTORY as u32;
self.messages[next_head as usize] = *message;
self.queue_head = next_head;
}
}
impl MessageBuffer {
/// Unpacks a buffer of
pub fn unpack(input: &mut [u8]) -> Result<&mut MessageBuffer, ProgramError> {
if input.len() != std::mem::size_of::<MessageBuffer>() {
Err(MessageError::MessageQueueAccountWrongSize)?;
}
assert_eq!(
std::mem::align_of::<MessageBuffer>(),
std::mem::align_of::<u8>()
);
let buffer = unsafe { &mut *(input.as_mut_ptr() as *mut MessageBuffer) };
if buffer.queue_head >= MESSAGE_HISTORY as u32 {
Err(MessageError::MessageQueueBad)?;
}
Ok(buffer)
}
}
impl Message {
/// Unpacks a
pub fn unpack(input: &[u8]) -> Result<Message, ProgramError> {
if input.len() > std::mem::size_of::<Message>() {
Err(MessageError::MessageTooLarge)?;
}
if input.len() < std::mem::size_of::<Message>() {
Err(MessageError::MessageTooSmall)?;
}
assert_eq!(input.len(), std::mem::size_of::<Message>());
let (key, rest) = input.split_at(std::mem::size_of::<Pubkey>());
let (length, rest) = rest.split_first().unwrap();
let writer = Pubkey::new(key);
let length = *length;
let mut bytes = [0; MESSAGE_LENGTH];
bytes.copy_from_slice(rest);
if length as usize > MESSAGE_LENGTH {
Err(MessageError::MessageLengthTooLarge)?;
}
if bytes[length as usize..].iter().any(|b| *b != 0) {
Err(MessageError::MessageLengthTooSmall)?;
}
if length == 0 {
Err(MessageError::MessageEmpty)?;
}
Ok(Message {
writer,
length,
bytes,
})
}
}

View File

@ -0,0 +1,34 @@
//! Program entrypoint
#![cfg(feature = "program")]
#![cfg(not(feature = "no-entrypoint"))]
use crate::buffer::{Message, MessageBuffer};
use crate::error::MessageError;
use solana_sdk::{
account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, pubkey::Pubkey,
};
entrypoint!(process_instruction);
fn process_instruction(_: &Pubkey, accounts: &[AccountInfo], instruction: &[u8]) -> ProgramResult {
if accounts.len() == 0 {
Err(MessageError::NoAccountsPassed)?
}
if accounts.len() == 1 {
Err(MessageError::QueueNotPassed)?
}
if accounts.len() > 2 {
Err(MessageError::ExtraAccountsPassed)?
}
let signer = &accounts[0];
let queue = &accounts[1];
if !signer.is_signer {
Err(MessageError::SenderDidNotSign)?;
}
let message = Message::unpack(instruction)?;
let mut queue_ref = queue.try_borrow_mut_data()?;
let message_buffer = MessageBuffer::unpack(&mut *queue_ref)?;
message_buffer.append(&message);
Ok(())
}

55
messaging/src/error.rs Normal file
View File

@ -0,0 +1,55 @@
//! Error types
use num_derive::FromPrimitive;
use solana_sdk::{decode_error::DecodeError, program_error::ProgramError};
use thiserror::Error;
/// Errors that may be returned by the Token program.
#[derive(Clone, Debug, Eq, Error, FromPrimitive, PartialEq)]
pub enum MessageError {
/// Message instruction is too large
#[error("Instruction size too large")]
MessageTooLarge,
/// Message instruction is too small
#[error("Instruction size too small")]
MessageTooSmall,
/// Message size is greater than max
#[error("Message length too large for instruction")]
MessageLengthTooLarge,
/// Message size is too small (nonzero data contained after length ends)
#[error("Message length too small")]
MessageLengthTooSmall,
/// Message contains no data
#[error("Message is empty")]
MessageEmpty,
/// Message queue buffer invalid size
#[error("Message queue account is the wrong size")]
MessageQueueAccountWrongSize,
/// Message queue head invalid
#[error("Message queue head is invalid")]
MessageQueueBad,
/// Signer and queue accounts not passed
#[error("Invocation expects [signer, queue] accounts to get passed")]
NoAccountsPassed,
/// Queue account not passed
#[error("Queue account not passed")]
QueueNotPassed,
/// Extra accounts passed
#[error("Extra accounts passed")]
ExtraAccountsPassed,
/// Sender did not sign
#[error("Sender must have signed the transaction")]
SenderDidNotSign,
}
impl From<MessageError> for ProgramError {
fn from(e: MessageError) -> Self {
ProgramError::Custom(e as u32)
}
}
impl<T> DecodeError<T> for MessageError {
fn type_of() -> &'static str {
"MessageError"
}
}

13
messaging/src/lib.rs Normal file
View File

@ -0,0 +1,13 @@
#![deny(missing_docs)]
//! A basic message buffer with limited history for a sending bytes around
pub mod buffer;
pub mod entrypoint;
pub mod error;
// Export current solana-sdk types for downstream users who may also be building with a different
// solana-sdk version
pub use solana_sdk;
solana_sdk::declare_id!("24CJEtjU3sjzNhzS3NarwcQwbZv68Zbgh1Kh86vQFX2f");