Compare commits
21 Commits
Author | SHA1 | Date | |
---|---|---|---|
68ec814bf3 | |||
d7f283a00a | |||
609889cc58 | |||
83fa02ee44 | |||
d8fcb22000 | |||
6b1c90a8b5 | |||
67d72e709f | |||
f0d2870e0f | |||
02c47b48da | |||
d30a39cd38 | |||
9e57d0467e | |||
fb57d13c03 | |||
41f6e27bba | |||
aff5649b39 | |||
9fd6ffe83f | |||
ff805361a9 | |||
30d1b0b4bf | |||
5233cf1ca6 | |||
d2754fd702 | |||
ce9a0ae215 | |||
067adcdfa8 |
@ -105,16 +105,16 @@ serde_cbor = "0.9.0"
|
||||
serde_derive = "1.0.27"
|
||||
serde_json = "1.0.10"
|
||||
socket2 = "0.3.8"
|
||||
solana_program_interface = { path = "common" }
|
||||
solana_program_interface = { path = "common", version="0.1.0" }
|
||||
sys-info = "0.5.6"
|
||||
tokio = "0.1"
|
||||
tokio-codec = "0.1"
|
||||
untrusted = "0.6.2"
|
||||
|
||||
[dev-dependencies]
|
||||
noop = { path = "programs/noop" }
|
||||
print = { path = "programs/print" }
|
||||
move_funds = { path = "programs/move_funds" }
|
||||
noop = { path = "programs/noop", version="0.1.0" }
|
||||
print = { path = "programs/print", version="0.1.0" }
|
||||
move_funds = { path = "programs/move_funds", version="0.1.0" }
|
||||
|
||||
[[bench]]
|
||||
name = "bank"
|
||||
|
7
ci/buildkite-secondary.yml
Normal file
7
ci/buildkite-secondary.yml
Normal file
@ -0,0 +1,7 @@
|
||||
steps:
|
||||
- command: "ci/snap.sh"
|
||||
timeout_in_minutes: 40
|
||||
name: "snap [public]"
|
||||
- command: "ci/docker-solana/build.sh"
|
||||
timeout_in_minutes: 20
|
||||
name: "docker-solana"
|
@ -1,4 +0,0 @@
|
||||
steps:
|
||||
- command: "ci/snap.sh"
|
||||
timeout_in_minutes: 40
|
||||
name: "snap [public]"
|
@ -39,7 +39,7 @@ steps:
|
||||
- command: "ci/publish-crate.sh"
|
||||
timeout_in_minutes: 20
|
||||
name: "publish crate [public]"
|
||||
- trigger: "solana-snap"
|
||||
- trigger: "solana-secondary"
|
||||
branches: "!pull/*"
|
||||
async: true
|
||||
build:
|
||||
|
1
ci/docker-solana/.gitignore
vendored
Normal file
1
ci/docker-solana/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
cargo-install/
|
13
ci/docker-solana/Dockerfile
Normal file
13
ci/docker-solana/Dockerfile
Normal file
@ -0,0 +1,13 @@
|
||||
FROM debian:stretch
|
||||
|
||||
# JSON RPC port
|
||||
EXPOSE 8899/tcp
|
||||
|
||||
# Install libssl
|
||||
RUN apt update && \
|
||||
apt-get install -y libssl-dev && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY usr/bin /usr/bin/
|
||||
ENTRYPOINT [ "/usr/bin/solana-entrypoint.sh" ]
|
||||
CMD [""]
|
17
ci/docker-solana/README.md
Normal file
17
ci/docker-solana/README.md
Normal file
@ -0,0 +1,17 @@
|
||||
## Minimal Solana Docker image
|
||||
This image is automatically updated by CI
|
||||
|
||||
https://hub.docker.com/r/solanalabs/solana/
|
||||
|
||||
### Usage:
|
||||
Run the latest beta image:
|
||||
```bash
|
||||
$ docker run --rm -p 8899:8899 solanalabs/solana:beta
|
||||
```
|
||||
|
||||
Run the latest edge image:
|
||||
```bash
|
||||
$ docker run --rm -p 8899:8899 solanalabs/solana:edge
|
||||
```
|
||||
|
||||
Port *8899* is the JSON RPC port, which is used by clients to communicate with the network.
|
38
ci/docker-solana/build.sh
Executable file
38
ci/docker-solana/build.sh
Executable file
@ -0,0 +1,38 @@
|
||||
#!/bin/bash -ex
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
eval "$(../channel-info.sh)"
|
||||
|
||||
if [[ $BUILDKITE_BRANCH = "$STABLE_CHANNEL" ]]; then
|
||||
CHANNEL=stable
|
||||
elif [[ $BUILDKITE_BRANCH = "$EDGE_CHANNEL" ]]; then
|
||||
CHANNEL=edge
|
||||
elif [[ $BUILDKITE_BRANCH = "$BETA_CHANNEL" ]]; then
|
||||
CHANNEL=beta
|
||||
fi
|
||||
|
||||
if [[ -z $CHANNEL ]]; then
|
||||
echo Unable to determine channel to publish into, exiting.
|
||||
exit 0
|
||||
fi
|
||||
|
||||
rm -rf usr/
|
||||
../docker-run.sh solanalabs/rust:1.29.1 \
|
||||
cargo install --path . --root ci/docker-solana/usr
|
||||
cp -f entrypoint.sh usr/bin/solana-entrypoint.sh
|
||||
|
||||
docker build -t solanalabs/solana:$CHANNEL .
|
||||
|
||||
maybeEcho=
|
||||
if [[ -z $CI ]]; then
|
||||
echo "Not CI, skipping |docker push|"
|
||||
maybeEcho="echo"
|
||||
else
|
||||
(
|
||||
set +x
|
||||
if [[ -n $DOCKER_PASSWORD && -n $DOCKER_USERNAME ]]; then
|
||||
echo "$DOCKER_PASSWORD" | docker login --username "$DOCKER_USERNAME" --password-stdin
|
||||
fi
|
||||
)
|
||||
fi
|
||||
$maybeEcho docker push solanalabs/solana:$CHANNEL
|
23
ci/docker-solana/entrypoint.sh
Executable file
23
ci/docker-solana/entrypoint.sh
Executable file
@ -0,0 +1,23 @@
|
||||
#!/bin/bash -ex
|
||||
|
||||
export RUST_LOG=solana=info
|
||||
export RUST_BACKTRACE=1
|
||||
|
||||
solana-keygen -o /config/leader-keypair.json
|
||||
solana-keygen -o /config/drone-keypair.json
|
||||
|
||||
solana-genesis --tokens=1000000000 --ledger /ledger < /config/drone-keypair.json
|
||||
solana-fullnode-config --keypair=/config/leader-keypair.json -l > /config/leader-config.json
|
||||
|
||||
solana-drone --keypair /config/drone-keypair.json --network 127.0.0.1:8001 &
|
||||
drone=$!
|
||||
solana-fullnode --identity /config/leader-config.json --ledger /ledger/ &
|
||||
fullnode=$!
|
||||
|
||||
abort() {
|
||||
kill "$drone" "$fullnode"
|
||||
}
|
||||
|
||||
trap abort SIGINT SIGTERM
|
||||
wait "$fullnode"
|
||||
kill "$drone" "$fullnode"
|
14
ci/snap.sh
14
ci/snap.sh
@ -10,14 +10,14 @@ fi
|
||||
eval "$(ci/channel-info.sh)"
|
||||
|
||||
if [[ $BUILDKITE_BRANCH = "$STABLE_CHANNEL" ]]; then
|
||||
SNAP_CHANNEL=stable
|
||||
CHANNEL=stable
|
||||
elif [[ $BUILDKITE_BRANCH = "$EDGE_CHANNEL" ]]; then
|
||||
SNAP_CHANNEL=edge
|
||||
CHANNEL=edge
|
||||
elif [[ $BUILDKITE_BRANCH = "$BETA_CHANNEL" ]]; then
|
||||
SNAP_CHANNEL=beta
|
||||
CHANNEL=beta
|
||||
fi
|
||||
|
||||
if [[ -z $SNAP_CHANNEL ]]; then
|
||||
if [[ -z $CHANNEL ]]; then
|
||||
echo Unable to determine channel to publish into, exiting.
|
||||
exit 0
|
||||
fi
|
||||
@ -51,11 +51,11 @@ if [[ ! -x /usr/bin/multilog ]]; then
|
||||
sudo apt-get install -y daemontools
|
||||
fi
|
||||
|
||||
echo --- build: $SNAP_CHANNEL channel
|
||||
echo --- build: $CHANNEL channel
|
||||
snapcraft
|
||||
|
||||
source ci/upload_ci_artifact.sh
|
||||
upload_ci_artifact solana_*.snap
|
||||
|
||||
echo --- publish: $SNAP_CHANNEL channel
|
||||
$DRYRUN snapcraft push solana_*.snap --release $SNAP_CHANNEL
|
||||
echo --- publish: $CHANNEL channel
|
||||
$DRYRUN snapcraft push solana_*.snap --release $CHANNEL
|
||||
|
@ -14,7 +14,6 @@ _() {
|
||||
_ cargo fmt -- --check
|
||||
_ cargo build --verbose
|
||||
_ cargo test --verbose
|
||||
_ cargo clippy -- --deny=warnings
|
||||
|
||||
echo --- ci/localnet-sanity.sh
|
||||
(
|
||||
|
@ -24,7 +24,7 @@ usage: $0 [name] [zone] [options...]
|
||||
Deploys a CD testnet
|
||||
|
||||
name - name of the network
|
||||
zone - GCE to deploy the network into
|
||||
zone - zone to deploy the network into
|
||||
|
||||
options:
|
||||
-s edge|beta|stable - Deploy the specified Snap release channel
|
||||
@ -116,7 +116,7 @@ fi
|
||||
set -x
|
||||
|
||||
echo --- gce.sh delete
|
||||
time net/gce.sh delete -p "$netName"
|
||||
time net/gce.sh delete -z "$zone" -p "$netName"
|
||||
if $delete; then
|
||||
exit 0
|
||||
fi
|
||||
|
@ -9,11 +9,12 @@ usage() {
|
||||
echo "Error: $*"
|
||||
fi
|
||||
cat <<EOF
|
||||
usage: $0 [name]
|
||||
usage: $0 [name] [zone]
|
||||
|
||||
Sanity check a CD testnet
|
||||
|
||||
name - name of the network
|
||||
zone - zone of the network
|
||||
|
||||
Note: the SOLANA_METRICS_CONFIG environment variable is used to configure
|
||||
metrics
|
||||
@ -22,11 +23,13 @@ EOF
|
||||
}
|
||||
|
||||
netName=$1
|
||||
zone=$2
|
||||
[[ -n $netName ]] || usage ""
|
||||
[[ -n $zone ]] || usage "Zone not specified"
|
||||
|
||||
set -x
|
||||
echo --- gce.sh config
|
||||
net/gce.sh config -p "$netName"
|
||||
net/gce.sh config -p "$netName" -z "$zone"
|
||||
net/init-metrics.sh -e
|
||||
echo --- net.sh sanity
|
||||
net/net.sh sanity \
|
||||
|
@ -31,11 +31,7 @@ __cloud_FindInstances() {
|
||||
|
||||
declare name zone publicIp privateIp status
|
||||
while read -r name publicIp privateIp status; do
|
||||
if [[ $status != RUNNING ]]; then
|
||||
echo "Warning: $name is not RUNNING, ignoring it."
|
||||
continue
|
||||
fi
|
||||
printf "%-30s | publicIp=%-16s privateIp=%s\n" "$name" "$publicIp" "$privateIp"
|
||||
printf "%-30s | publicIp=%-16s privateIp=%s staus=%s\n" "$name" "$publicIp" "$privateIp" "$status"
|
||||
|
||||
instances+=("$name:$publicIp:$privateIp")
|
||||
done < <(gcloud compute instances list \
|
||||
|
@ -13,8 +13,8 @@ sysctl -w kernel.sysrq=$(( $(cat /proc/sys/kernel/sysrq) | 64 ))
|
||||
if command -v earlyoom; then
|
||||
systemctl status earlyoom
|
||||
else
|
||||
wget http://ftp.us.debian.org/debian/pool/main/e/earlyoom/earlyoom_1.1-2_amd64.deb
|
||||
apt install --quiet --yes ./earlyoom_1.1-2_amd64.deb
|
||||
wget -r -l1 -np http://ftp.us.debian.org/debian/pool/main/e/earlyoom/ -A 'earlyoom_1.1-*_amd64.deb' -e robots=off -nd
|
||||
apt install --quiet --yes ./earlyoom_1.1-*_amd64.deb
|
||||
|
||||
cat > earlyoom <<OOM
|
||||
# use the kernel OOM killer, trigger at 20% available RAM,
|
||||
|
@ -33,6 +33,7 @@ pay_and_confirm() {
|
||||
$solana_wallet "${entrypoint[@]}" confirm "$signature"
|
||||
}
|
||||
|
||||
$solana_keygen
|
||||
$solana_wallet "${entrypoint[@]}" address
|
||||
check_balance_output "No account found" "Your balance is: 0"
|
||||
$solana_wallet "${entrypoint[@]}" airdrop 60
|
||||
|
10
src/bank.rs
10
src/bank.rs
@ -28,8 +28,10 @@ use std::time::Instant;
|
||||
use storage_program::StorageProgram;
|
||||
use system_program::SystemProgram;
|
||||
use system_transaction::SystemTransaction;
|
||||
use tictactoe_dashboard_program::TicTacToeDashboardProgram;
|
||||
use tictactoe_program::TicTacToeProgram;
|
||||
use timing::{duration_as_us, timestamp};
|
||||
use token_program::TokenProgram;
|
||||
use transaction::Transaction;
|
||||
use window::WINDOW_SIZE;
|
||||
|
||||
@ -405,6 +407,14 @@ impl Bank {
|
||||
if TicTacToeProgram::process_transaction(&tx, accounts).is_err() {
|
||||
return Err(BankError::ProgramRuntimeError);
|
||||
}
|
||||
} else if TicTacToeDashboardProgram::check_id(&tx.program_id) {
|
||||
if TicTacToeDashboardProgram::process_transaction(&tx, accounts).is_err() {
|
||||
return Err(BankError::ProgramRuntimeError);
|
||||
}
|
||||
} else if TokenProgram::check_id(&tx.program_id) {
|
||||
if TokenProgram::process_transaction(&tx, accounts).is_err() {
|
||||
return Err(BankError::ProgramRuntimeError);
|
||||
}
|
||||
} else if self.loaded_contract(&tx, accounts) {
|
||||
} else {
|
||||
return Err(BankError::UnknownContractId);
|
||||
|
@ -4,7 +4,6 @@
|
||||
|
||||
use bank::Bank;
|
||||
use bincode::deserialize;
|
||||
use budget_transaction::BudgetTransaction;
|
||||
use counter::Counter;
|
||||
use entry::Entry;
|
||||
use log::Level;
|
||||
@ -224,7 +223,7 @@ impl BankingStage {
|
||||
.zip(vers)
|
||||
.filter_map(|(tx, ver)| match tx {
|
||||
None => None,
|
||||
Some((tx, _addr)) => if tx.verify_plan() && ver != 0 {
|
||||
Some((tx, _addr)) => if ver != 0 {
|
||||
Some(tx)
|
||||
} else {
|
||||
None
|
||||
|
@ -526,7 +526,7 @@ impl Crdt {
|
||||
received_index: u64,
|
||||
) -> Result<()> {
|
||||
if broadcast_table.is_empty() {
|
||||
warn!("{}:not enough peers in crdt table", me.id);
|
||||
debug!("{}:not enough peers in crdt table", me.id);
|
||||
inc_new_counter_info!("crdt-broadcast-not_enough_peers_error", 1);
|
||||
Err(CrdtError::NoPeers)?;
|
||||
}
|
||||
|
@ -60,8 +60,10 @@ pub mod streamer;
|
||||
pub mod system_program;
|
||||
pub mod system_transaction;
|
||||
pub mod thin_client;
|
||||
pub mod tictactoe_dashboard_program;
|
||||
pub mod tictactoe_program;
|
||||
pub mod timing;
|
||||
pub mod token_program;
|
||||
pub mod tpu;
|
||||
pub mod transaction;
|
||||
pub mod tvu;
|
||||
|
@ -43,7 +43,7 @@ impl JsonRpcService {
|
||||
io.extend_with(rpc.to_delegate());
|
||||
|
||||
let server =
|
||||
ServerBuilder::with_meta_extractor(io, move |_req: &hyper::Request| Meta {
|
||||
ServerBuilder::with_meta_extractor(io, move |_req: &hyper::Request<hyper::Body>| Meta {
|
||||
request_processor: request_processor.clone(),
|
||||
transactions_addr,
|
||||
drone_addr,
|
||||
|
168
src/tictactoe_dashboard_program.rs
Normal file
168
src/tictactoe_dashboard_program.rs
Normal file
@ -0,0 +1,168 @@
|
||||
//! tic-tac-toe dashboard program
|
||||
|
||||
use serde_cbor;
|
||||
use solana_program_interface::account::Account;
|
||||
use solana_program_interface::pubkey::Pubkey;
|
||||
use tictactoe_program::{Error, Game, Result, State, TicTacToeProgram};
|
||||
use transaction::Transaction;
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize, PartialEq)]
|
||||
pub struct TicTacToeDashboardProgram {
|
||||
pending: Pubkey, // Latest pending game
|
||||
completed: Vec<Pubkey>, // Last N completed games (0 is the latest)
|
||||
total: usize, // Total number of completed games
|
||||
}
|
||||
|
||||
pub const TICTACTOE_DASHBOARD_PROGRAM_ID: [u8; 32] = [
|
||||
4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
];
|
||||
|
||||
impl TicTacToeDashboardProgram {
|
||||
fn deserialize(input: &[u8]) -> Result<TicTacToeDashboardProgram> {
|
||||
if input.len() < 2 {
|
||||
Err(Error::InvalidUserdata)?;
|
||||
}
|
||||
let len = input[0] as usize + (0x100 * input[1] as usize);
|
||||
if len == 0 {
|
||||
Ok(TicTacToeDashboardProgram::default())
|
||||
} else if input.len() < len + 2 {
|
||||
Err(Error::InvalidUserdata)
|
||||
} else {
|
||||
serde_cbor::from_slice(&input[2..(2 + len)]).map_err(|err| {
|
||||
error!("Unable to deserialize: {:?}", err);
|
||||
Error::InvalidUserdata
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn update(
|
||||
self: &mut TicTacToeDashboardProgram,
|
||||
game_pubkey: &Pubkey,
|
||||
game: &Game,
|
||||
) -> Result<()> {
|
||||
match game.state {
|
||||
State::Waiting => {
|
||||
self.pending = *game_pubkey;
|
||||
}
|
||||
State::XMove | State::OMove => {
|
||||
// Nothing to do. In progress games are not managed by the dashboard
|
||||
}
|
||||
State::XWon | State::OWon | State::Draw => {
|
||||
if !self.completed.iter().any(|pubkey| pubkey == game_pubkey) {
|
||||
// TODO: Once the PoH high is exposed to programs, it could be used to ensure
|
||||
// that old games are not being re-added and causing |total| to increment
|
||||
// incorrectly.
|
||||
self.total += 1;
|
||||
self.completed.insert(0, *game_pubkey);
|
||||
|
||||
// Only track the last N completed games to
|
||||
// avoid overrunning Account userdata
|
||||
if self.completed.len() > 5 {
|
||||
self.completed.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn serialize(self: &TicTacToeDashboardProgram, output: &mut [u8]) -> Result<()> {
|
||||
let self_serialized = serde_cbor::to_vec(self).unwrap();
|
||||
|
||||
if output.len() < 2 + self_serialized.len() {
|
||||
warn!(
|
||||
"{} bytes required to serialize but only have {} bytes",
|
||||
self_serialized.len() + 2,
|
||||
output.len()
|
||||
);
|
||||
return Err(Error::UserdataTooSmall);
|
||||
}
|
||||
|
||||
assert!(self_serialized.len() <= 0xFFFF);
|
||||
output[0] = (self_serialized.len() & 0xFF) as u8;
|
||||
output[1] = (self_serialized.len() >> 8) as u8;
|
||||
output[2..(2 + self_serialized.len())].clone_from_slice(&self_serialized);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn check_id(program_id: &Pubkey) -> bool {
|
||||
program_id.as_ref() == TICTACTOE_DASHBOARD_PROGRAM_ID
|
||||
}
|
||||
|
||||
pub fn id() -> Pubkey {
|
||||
Pubkey::new(&TICTACTOE_DASHBOARD_PROGRAM_ID)
|
||||
}
|
||||
|
||||
pub fn process_transaction(tx: &Transaction, accounts: &mut [Account]) -> Result<()> {
|
||||
info!("process_transaction: {:?}", tx);
|
||||
// accounts[0] doesn't matter, anybody can cause a dashboard update
|
||||
// accounts[1] must be a Dashboard account
|
||||
// accounts[2] must be a Game account
|
||||
if accounts.len() != 3 {
|
||||
error!("Expected 3 accounts");
|
||||
Err(Error::InvalidArguments)?;
|
||||
}
|
||||
if !Self::check_id(&accounts[1].program_id) {
|
||||
error!("accounts[1] is not a TICTACTOE_DASHBOARD_PROGRAM_ID");
|
||||
Err(Error::InvalidArguments)?;
|
||||
}
|
||||
if accounts[1].userdata.is_empty() {
|
||||
error!("accounts[1] userdata is empty");
|
||||
Err(Error::InvalidArguments)?;
|
||||
}
|
||||
|
||||
let mut dashboard = Self::deserialize(&accounts[1].userdata)?;
|
||||
|
||||
if !TicTacToeProgram::check_id(&accounts[2].program_id) {
|
||||
error!("accounts[2] is not a TICTACTOE_PROGRAM_ID");
|
||||
Err(Error::InvalidArguments)?;
|
||||
}
|
||||
let ttt = TicTacToeProgram::deserialize(&accounts[2].userdata)?;
|
||||
|
||||
match ttt.game {
|
||||
None => Err(Error::NoGame),
|
||||
Some(game) => dashboard.update(&tx.keys[2], &game),
|
||||
}?;
|
||||
|
||||
dashboard.serialize(&mut accounts[1].userdata)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
pub fn serde() {
|
||||
let mut dashboard1 = TicTacToeDashboardProgram::default();
|
||||
dashboard1.total = 1234567890;
|
||||
dashboard1.pending = Pubkey::new(&[100; 32]);
|
||||
for i in 1..5 {
|
||||
dashboard1.completed.push(Pubkey::new(&[100 + i; 32]));
|
||||
}
|
||||
|
||||
let mut userdata = vec![0xff; 512];
|
||||
dashboard1.serialize(&mut userdata).unwrap();
|
||||
|
||||
let dashboard2 = TicTacToeDashboardProgram::deserialize(&userdata).unwrap();
|
||||
assert_eq!(dashboard1, dashboard2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn serde_userdata_too_small() {
|
||||
let dashboard = TicTacToeDashboardProgram::default();
|
||||
let mut userdata = vec![0xff; 1];
|
||||
assert_eq!(
|
||||
dashboard.serialize(&mut userdata),
|
||||
Err(Error::UserdataTooSmall)
|
||||
);
|
||||
|
||||
let err = TicTacToeDashboardProgram::deserialize(&userdata);
|
||||
assert!(err.is_err());
|
||||
assert_eq!(err.err().unwrap(), Error::InvalidUserdata);
|
||||
}
|
||||
|
||||
// TODO: add tests for business logic
|
||||
}
|
@ -12,6 +12,7 @@ pub enum Error {
|
||||
InvalidArguments,
|
||||
InvalidMove,
|
||||
InvalidUserdata,
|
||||
InvalidTimestamp,
|
||||
NoGame,
|
||||
NotYourTurn,
|
||||
PlayerNotFound,
|
||||
@ -24,25 +25,24 @@ impl std::fmt::Display for Error {
|
||||
}
|
||||
impl std::error::Error for Error {}
|
||||
|
||||
type Result<T> = std::result::Result<T, Error>;
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
enum GridItem {
|
||||
Free,
|
||||
enum BoardItem {
|
||||
F, // Free
|
||||
X,
|
||||
O,
|
||||
}
|
||||
|
||||
impl Default for GridItem {
|
||||
fn default() -> GridItem {
|
||||
GridItem::Free
|
||||
impl Default for BoardItem {
|
||||
fn default() -> BoardItem {
|
||||
BoardItem::F
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
enum State {
|
||||
WaitingForO,
|
||||
ORequestPending,
|
||||
pub enum State {
|
||||
Waiting,
|
||||
XMove,
|
||||
OMove,
|
||||
XWon,
|
||||
@ -51,131 +51,138 @@ enum State {
|
||||
}
|
||||
impl Default for State {
|
||||
fn default() -> State {
|
||||
State::WaitingForO
|
||||
State::Waiting
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize, PartialEq)]
|
||||
struct Game {
|
||||
pub struct Game {
|
||||
player_x: Pubkey,
|
||||
player_o: Option<Pubkey>,
|
||||
state: State,
|
||||
grid: [GridItem; 9],
|
||||
pub state: State,
|
||||
board: [BoardItem; 9],
|
||||
keep_alive: [i64; 2],
|
||||
}
|
||||
|
||||
impl Game {
|
||||
pub fn create(player_x: &Pubkey) -> Game {
|
||||
let mut game = Game::default();
|
||||
game.player_x = *player_x;
|
||||
assert_eq!(game.state, State::WaitingForO);
|
||||
assert_eq!(game.state, State::Waiting);
|
||||
game
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn new(player_x: Pubkey, player_o: Pubkey) -> Game {
|
||||
let mut game = Game::create(&player_x);
|
||||
game.join(&player_o).unwrap();
|
||||
game.accept().unwrap();
|
||||
game.join(player_o, 1).unwrap();
|
||||
game
|
||||
}
|
||||
|
||||
pub fn join(self: &mut Game, player_o: &Pubkey) -> Result<()> {
|
||||
if self.state == State::WaitingForO {
|
||||
self.player_o = Some(*player_o);
|
||||
self.state = State::ORequestPending;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::NotYourTurn)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn accept(self: &mut Game) -> Result<()> {
|
||||
if self.state == State::ORequestPending {
|
||||
assert!(self.player_o.is_some());
|
||||
pub fn join(self: &mut Game, player_o: Pubkey, timestamp: i64) -> Result<()> {
|
||||
if self.state == State::Waiting {
|
||||
self.player_o = Some(player_o);
|
||||
self.state = State::XMove;
|
||||
Ok(())
|
||||
|
||||
if timestamp <= self.keep_alive[1] {
|
||||
Err(Error::InvalidTimestamp)
|
||||
} else {
|
||||
self.keep_alive[1] = timestamp;
|
||||
Ok(())
|
||||
}
|
||||
} else {
|
||||
Err(Error::NotYourTurn)
|
||||
Err(Error::GameInProgress)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reject(self: &mut Game) -> Result<()> {
|
||||
if self.state == State::ORequestPending {
|
||||
assert!(self.player_o.is_some());
|
||||
self.player_o = None;
|
||||
self.state = State::WaitingForO;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::NotYourTurn)
|
||||
}
|
||||
}
|
||||
|
||||
fn same(x_or_o: GridItem, triple: &[GridItem]) -> bool {
|
||||
fn same(x_or_o: BoardItem, triple: &[BoardItem]) -> bool {
|
||||
triple.iter().all(|&i| i == x_or_o)
|
||||
}
|
||||
|
||||
pub fn next_move(self: &mut Game, player: Pubkey, x: usize, y: usize) -> Result<()> {
|
||||
let grid_index = y * 3 + x;
|
||||
if grid_index >= self.grid.len() || self.grid[grid_index] != GridItem::Free {
|
||||
return Err(Error::InvalidMove);
|
||||
let board_index = y * 3 + x;
|
||||
if board_index >= self.board.len() || self.board[board_index] != BoardItem::F {
|
||||
Err(Error::InvalidMove)?;
|
||||
}
|
||||
|
||||
let (x_or_o, won_state) = match self.state {
|
||||
State::XMove => {
|
||||
if player != self.player_x {
|
||||
return Err(Error::PlayerNotFound)?;
|
||||
return Err(Error::PlayerNotFound);
|
||||
}
|
||||
self.state = State::OMove;
|
||||
(GridItem::X, State::XWon)
|
||||
(BoardItem::X, State::XWon)
|
||||
}
|
||||
State::OMove => {
|
||||
if player != self.player_o.unwrap() {
|
||||
return Err(Error::PlayerNotFound)?;
|
||||
return Err(Error::PlayerNotFound);
|
||||
}
|
||||
self.state = State::XMove;
|
||||
(GridItem::O, State::OWon)
|
||||
(BoardItem::O, State::OWon)
|
||||
}
|
||||
_ => {
|
||||
return Err(Error::NotYourTurn)?;
|
||||
return Err(Error::NotYourTurn);
|
||||
}
|
||||
};
|
||||
self.grid[grid_index] = x_or_o;
|
||||
self.board[board_index] = x_or_o;
|
||||
|
||||
let winner =
|
||||
// Check rows
|
||||
Game::same(x_or_o, &self.grid[0..3])
|
||||
|| Game::same(x_or_o, &self.grid[3..6])
|
||||
|| Game::same(x_or_o, &self.grid[6..9])
|
||||
Game::same(x_or_o, &self.board[0..3])
|
||||
|| Game::same(x_or_o, &self.board[3..6])
|
||||
|| Game::same(x_or_o, &self.board[6..9])
|
||||
// Check columns
|
||||
|| Game::same(x_or_o, &[self.grid[0], self.grid[3], self.grid[6]])
|
||||
|| Game::same(x_or_o, &[self.grid[1], self.grid[4], self.grid[7]])
|
||||
|| Game::same(x_or_o, &[self.grid[2], self.grid[5], self.grid[8]])
|
||||
|| Game::same(x_or_o, &[self.board[0], self.board[3], self.board[6]])
|
||||
|| Game::same(x_or_o, &[self.board[1], self.board[4], self.board[7]])
|
||||
|| Game::same(x_or_o, &[self.board[2], self.board[5], self.board[8]])
|
||||
// Check both diagonals
|
||||
|| Game::same(x_or_o, &[self.grid[0], self.grid[4], self.grid[8]])
|
||||
|| Game::same(x_or_o, &[self.grid[2], self.grid[4], self.grid[6]]);
|
||||
|| Game::same(x_or_o, &[self.board[0], self.board[4], self.board[8]])
|
||||
|| Game::same(x_or_o, &[self.board[2], self.board[4], self.board[6]]);
|
||||
|
||||
if winner {
|
||||
self.state = won_state;
|
||||
} else if self.grid.iter().all(|&p| p != GridItem::Free) {
|
||||
} else if self.board.iter().all(|&p| p != BoardItem::F) {
|
||||
self.state = State::Draw;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn keep_alive(self: &mut Game, player: Pubkey, timestamp: i64) -> Result<()> {
|
||||
match self.state {
|
||||
State::Waiting | State::XMove | State::OMove => {
|
||||
if player == self.player_x {
|
||||
if timestamp <= self.keep_alive[0] {
|
||||
Err(Error::InvalidTimestamp)?;
|
||||
}
|
||||
self.keep_alive[0] = timestamp;
|
||||
} else if Some(player) == self.player_o {
|
||||
if timestamp <= self.keep_alive[1] {
|
||||
Err(Error::InvalidTimestamp)?;
|
||||
}
|
||||
self.keep_alive[1] = timestamp;
|
||||
} else {
|
||||
Err(Error::PlayerNotFound)?;
|
||||
}
|
||||
}
|
||||
// Ignore keep_alive when game is no longer in progress
|
||||
State::XWon | State::OWon | State::Draw => {}
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
enum Command {
|
||||
Init, // player X initializes a new game
|
||||
Join, // player O wants to join
|
||||
Accept, // player X accepts the Join request
|
||||
Reject, // player X rejects the Join request
|
||||
Move(u8, u8), // player X/O mark board position (x, y)
|
||||
Init, // player X initializes a new game
|
||||
Join(i64), // player O wants to join (seconds since UNIX epoch)
|
||||
KeepAlive(i64), // player X/O keep alive (seconds since UNIX epoch)
|
||||
Move(u8, u8), // player X/O mark board position (x, y)
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
||||
pub struct TicTacToeProgram {
|
||||
game: Option<Game>,
|
||||
pub game: Option<Game>,
|
||||
}
|
||||
|
||||
pub const TICTACTOE_PROGRAM_ID: [u8; 32] = [
|
||||
@ -183,7 +190,7 @@ pub const TICTACTOE_PROGRAM_ID: [u8; 32] = [
|
||||
];
|
||||
|
||||
impl TicTacToeProgram {
|
||||
fn deserialize(input: &[u8]) -> Result<TicTacToeProgram> {
|
||||
pub fn deserialize(input: &[u8]) -> Result<TicTacToeProgram> {
|
||||
let len = input[0] as usize;
|
||||
|
||||
if len == 0 {
|
||||
@ -214,22 +221,9 @@ impl TicTacToeProgram {
|
||||
|
||||
if let Some(ref mut game) = self.game {
|
||||
match cmd {
|
||||
Command::Join => game.join(player),
|
||||
Command::Accept => {
|
||||
if *player == game.player_x {
|
||||
game.accept()
|
||||
} else {
|
||||
Err(Error::PlayerNotFound)
|
||||
}
|
||||
}
|
||||
Command::Reject => {
|
||||
if *player == game.player_x {
|
||||
game.reject()
|
||||
} else {
|
||||
Err(Error::PlayerNotFound)
|
||||
}
|
||||
}
|
||||
Command::Join(timestamp) => game.join(*player, *timestamp),
|
||||
Command::Move(x, y) => game.next_move(*player, *x as usize, *y as usize),
|
||||
Command::KeepAlive(timestamp) => game.keep_alive(*player, *timestamp),
|
||||
Command::Init => panic!("Unreachable"),
|
||||
}
|
||||
} else {
|
||||
|
404
src/token_program.rs
Normal file
404
src/token_program.rs
Normal file
@ -0,0 +1,404 @@
|
||||
//! ERC20-like Token program
|
||||
|
||||
use bincode;
|
||||
|
||||
use solana_program_interface::account::Account;
|
||||
use solana_program_interface::pubkey::Pubkey;
|
||||
use std;
|
||||
use transaction::Transaction;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Error {
|
||||
InvalidArgument,
|
||||
InsufficentFunds,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(f, "error")
|
||||
}
|
||||
}
|
||||
impl std::error::Error for Error {}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize, PartialEq)]
|
||||
pub struct TokenInfo {
|
||||
/**
|
||||
* Total supply of tokens
|
||||
*/
|
||||
supply: u64,
|
||||
|
||||
/**
|
||||
* Number of base 10 digits to the right of the decimal place in the total supply
|
||||
*/
|
||||
decimals: u8,
|
||||
|
||||
/**
|
||||
* Descriptive name of this token
|
||||
*/
|
||||
name: String,
|
||||
|
||||
/**
|
||||
* Symbol for this token
|
||||
*/
|
||||
symbol: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct TokenAccountInfo {
|
||||
/**
|
||||
* The kind of token this account holds
|
||||
*/
|
||||
token: Pubkey,
|
||||
|
||||
/**
|
||||
* Owner of this account
|
||||
*/
|
||||
owner: Pubkey,
|
||||
|
||||
/**
|
||||
* Amount of tokens this account holds
|
||||
*/
|
||||
amount: u64,
|
||||
|
||||
/**
|
||||
* The source account for the tokens.
|
||||
*
|
||||
* If `source` is None, `amount` belongs to this account.
|
||||
* If `source` is Option<>, `amount` represents an allowance of tokens that
|
||||
* may be transferred from the `source` account.
|
||||
*/
|
||||
source: Option<Pubkey>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
enum Command {
|
||||
NewToken(TokenInfo),
|
||||
NewTokenAccount(),
|
||||
Transfer(u64),
|
||||
Approve(u64),
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub enum TokenProgram {
|
||||
Unallocated,
|
||||
Token(TokenInfo),
|
||||
Account(TokenAccountInfo),
|
||||
Invalid,
|
||||
}
|
||||
impl Default for TokenProgram {
|
||||
fn default() -> TokenProgram {
|
||||
TokenProgram::Unallocated
|
||||
}
|
||||
}
|
||||
|
||||
pub const TOKEN_PROGRAM_ID: [u8; 32] = [
|
||||
5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
];
|
||||
|
||||
impl TokenProgram {
|
||||
fn to_invalid_args(err: std::boxed::Box<bincode::ErrorKind>) -> Error {
|
||||
warn!("invalid argument: {:?}", err);
|
||||
Error::InvalidArgument
|
||||
}
|
||||
|
||||
fn deserialize(input: &[u8]) -> Result<TokenProgram> {
|
||||
if input.len() < 1 {
|
||||
Err(Error::InvalidArgument)?;
|
||||
}
|
||||
match input[0] {
|
||||
0 => Ok(TokenProgram::Unallocated),
|
||||
1 => Ok(TokenProgram::Token(
|
||||
bincode::deserialize(&input[1..]).map_err(Self::to_invalid_args)?,
|
||||
)),
|
||||
2 => Ok(TokenProgram::Account(
|
||||
bincode::deserialize(&input[1..]).map_err(Self::to_invalid_args)?,
|
||||
)),
|
||||
_ => Err(Error::InvalidArgument),
|
||||
}
|
||||
}
|
||||
|
||||
fn serialize(self: &TokenProgram, output: &mut [u8]) -> Result<()> {
|
||||
if output.len() == 0 {
|
||||
warn!("serialize fail: ouput.len is 0");
|
||||
Err(Error::InvalidArgument)?;
|
||||
}
|
||||
match self {
|
||||
TokenProgram::Unallocated | TokenProgram::Invalid => Err(Error::InvalidArgument),
|
||||
TokenProgram::Token(token_info) => {
|
||||
output[0] = 1;
|
||||
let writer = std::io::BufWriter::new(&mut output[1..]);
|
||||
bincode::serialize_into(writer, &token_info).map_err(Self::to_invalid_args)
|
||||
}
|
||||
TokenProgram::Account(account_info) => {
|
||||
output[0] = 2;
|
||||
let writer = std::io::BufWriter::new(&mut output[1..]);
|
||||
bincode::serialize_into(writer, &account_info).map_err(Self::to_invalid_args)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_id(program_id: &Pubkey) -> bool {
|
||||
program_id.as_ref() == TOKEN_PROGRAM_ID
|
||||
}
|
||||
|
||||
pub fn id() -> Pubkey {
|
||||
Pubkey::new(&TOKEN_PROGRAM_ID)
|
||||
}
|
||||
|
||||
pub fn process_transaction(tx: &Transaction, accounts: &mut [Account]) -> Result<()> {
|
||||
let command =
|
||||
bincode::deserialize::<Command>(&tx.userdata).map_err(Self::to_invalid_args)?;
|
||||
info!("process_transaction: command={:?}", command);
|
||||
|
||||
let input_program_accounts: Vec<TokenProgram> = accounts
|
||||
.iter()
|
||||
.map(|account| {
|
||||
if Self::check_id(&account.program_id) {
|
||||
Self::deserialize(&account.userdata)
|
||||
.map_err(|err| {
|
||||
info!("deserialize failed: {:?}", err);
|
||||
TokenProgram::Invalid
|
||||
}).unwrap()
|
||||
} else {
|
||||
TokenProgram::Invalid
|
||||
}
|
||||
}).collect();
|
||||
|
||||
//let mut output_program_accounts: Vec<(usize, TokenProgram)> = vec![];
|
||||
let mut output_program_accounts: Vec<(_, _)> = vec![];
|
||||
|
||||
match command {
|
||||
Command::NewToken(token_info) => {
|
||||
if accounts.len() != 2 {
|
||||
error!("Expected 2 accounts");
|
||||
Err(Error::InvalidArgument)?;
|
||||
}
|
||||
|
||||
if let TokenProgram::Account(dest_account) = &input_program_accounts[1] {
|
||||
if tx.keys[0] != dest_account.token {
|
||||
info!("account 1 token mismatch");
|
||||
Err(Error::InvalidArgument)?;
|
||||
}
|
||||
|
||||
if dest_account.source.is_some() {
|
||||
info!("account 1 is a delegate and cannot accept tokens");
|
||||
Err(Error::InvalidArgument)?;
|
||||
}
|
||||
|
||||
let mut output_dest_account = dest_account.clone();
|
||||
output_dest_account.amount = token_info.supply;
|
||||
output_program_accounts.push((1, TokenProgram::Account(output_dest_account)));
|
||||
} else {
|
||||
info!("account 1 invalid");
|
||||
Err(Error::InvalidArgument)?;
|
||||
}
|
||||
|
||||
if input_program_accounts[0] != TokenProgram::Unallocated {
|
||||
info!("account 0 not available");
|
||||
Err(Error::InvalidArgument)?;
|
||||
}
|
||||
output_program_accounts.push((0, TokenProgram::Token(token_info)));
|
||||
}
|
||||
Command::NewTokenAccount() => {
|
||||
// key 0 - Destination new token account
|
||||
// key 1 - Owner of the account
|
||||
// key 2 - Token this account is associated with
|
||||
// key 3 - Source account that this account is a delegate for (optional)
|
||||
if accounts.len() < 3 {
|
||||
error!("Expected 3 accounts");
|
||||
Err(Error::InvalidArgument)?;
|
||||
}
|
||||
if input_program_accounts[0] != TokenProgram::Unallocated {
|
||||
info!("account 0 is already allocated");
|
||||
Err(Error::InvalidArgument)?;
|
||||
}
|
||||
|
||||
let mut token_account_info = TokenAccountInfo {
|
||||
token: tx.keys[2],
|
||||
owner: tx.keys[1],
|
||||
amount: 0,
|
||||
source: None,
|
||||
};
|
||||
if accounts.len() >= 4 {
|
||||
token_account_info.source = Some(tx.keys[3]);
|
||||
}
|
||||
output_program_accounts.push((0, TokenProgram::Account(token_account_info)));
|
||||
}
|
||||
Command::Transfer(amount) => {
|
||||
if accounts.len() < 3 {
|
||||
error!("Expected 3 accounts");
|
||||
Err(Error::InvalidArgument)?;
|
||||
}
|
||||
|
||||
if let (
|
||||
TokenProgram::Account(source_account),
|
||||
TokenProgram::Account(dest_account),
|
||||
) = (&input_program_accounts[1], &input_program_accounts[2])
|
||||
{
|
||||
if source_account.token != dest_account.token {
|
||||
info!("account 1/2 token mismatch");
|
||||
Err(Error::InvalidArgument)?;
|
||||
}
|
||||
|
||||
if dest_account.source.is_some() {
|
||||
info!("account 2 is a delegate and cannot accept tokens");
|
||||
Err(Error::InvalidArgument)?;
|
||||
}
|
||||
|
||||
if source_account.owner != tx.keys[0] {
|
||||
info!("owner of account 1 not present");
|
||||
Err(Error::InvalidArgument)?;
|
||||
}
|
||||
|
||||
if source_account.amount < amount {
|
||||
Err(Error::InsufficentFunds)?;
|
||||
}
|
||||
|
||||
let mut output_source_account = source_account.clone();
|
||||
output_source_account.amount -= amount;
|
||||
output_program_accounts.push((1, TokenProgram::Account(output_source_account)));
|
||||
|
||||
if source_account.source.is_some() {
|
||||
if accounts.len() != 4 {
|
||||
error!("Expected 4 accounts");
|
||||
Err(Error::InvalidArgument)?;
|
||||
}
|
||||
|
||||
let delegate_account = source_account;
|
||||
if let TokenProgram::Account(source_account) = &input_program_accounts[3] {
|
||||
if source_account.token != delegate_account.token {
|
||||
info!("account 1/3 token mismatch");
|
||||
Err(Error::InvalidArgument)?;
|
||||
}
|
||||
if delegate_account.source != Some(tx.keys[3]) {
|
||||
info!("Account 1 is not a delegate of account 3");
|
||||
Err(Error::InvalidArgument)?;
|
||||
}
|
||||
|
||||
if source_account.amount < amount {
|
||||
Err(Error::InsufficentFunds)?;
|
||||
}
|
||||
|
||||
let mut output_source_account = source_account.clone();
|
||||
output_source_account.amount -= amount;
|
||||
output_program_accounts
|
||||
.push((3, TokenProgram::Account(output_source_account)));
|
||||
} else {
|
||||
info!("account 3 is an invalid account");
|
||||
Err(Error::InvalidArgument)?;
|
||||
}
|
||||
}
|
||||
|
||||
let mut output_dest_account = dest_account.clone();
|
||||
output_dest_account.amount += amount;
|
||||
output_program_accounts.push((2, TokenProgram::Account(output_dest_account)));
|
||||
} else {
|
||||
info!("account 1 and/or 2 are invalid accounts");
|
||||
Err(Error::InvalidArgument)?;
|
||||
}
|
||||
}
|
||||
Command::Approve(amount) => {
|
||||
if accounts.len() != 3 {
|
||||
error!("Expected 3 accounts");
|
||||
Err(Error::InvalidArgument)?;
|
||||
}
|
||||
|
||||
if let (
|
||||
TokenProgram::Account(source_account),
|
||||
TokenProgram::Account(delegate_account),
|
||||
) = (&input_program_accounts[1], &input_program_accounts[2])
|
||||
{
|
||||
if source_account.token != delegate_account.token {
|
||||
info!("account 1/2 token mismatch");
|
||||
Err(Error::InvalidArgument)?;
|
||||
}
|
||||
|
||||
if source_account.owner != tx.keys[0] {
|
||||
info!("owner of account 1 not present");
|
||||
Err(Error::InvalidArgument)?;
|
||||
}
|
||||
|
||||
if source_account.source.is_some() {
|
||||
info!("account 1 is a delegate");
|
||||
Err(Error::InvalidArgument)?;
|
||||
}
|
||||
|
||||
if delegate_account.source != Some(tx.keys[1]) {
|
||||
info!("account 2 is not a delegate of account 1");
|
||||
Err(Error::InvalidArgument)?;
|
||||
}
|
||||
|
||||
let mut output_delegate_account = delegate_account.clone();
|
||||
output_delegate_account.amount = amount;
|
||||
output_program_accounts
|
||||
.push((2, TokenProgram::Account(output_delegate_account)));
|
||||
} else {
|
||||
info!("account 1 and/or 2 are invalid accounts");
|
||||
Err(Error::InvalidArgument)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (index, program_account) in output_program_accounts.iter() {
|
||||
info!(
|
||||
"output_program_account: index={} userdata={:?}",
|
||||
index, program_account
|
||||
);
|
||||
Self::serialize(program_account, &mut accounts[*index].userdata)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
#[test]
|
||||
pub fn serde() {
|
||||
assert_eq!(TokenProgram::deserialize(&[0]), Ok(TokenProgram::default()));
|
||||
|
||||
let mut userdata = vec![0; 256];
|
||||
|
||||
let account = TokenProgram::Account(TokenAccountInfo {
|
||||
token: Pubkey::new(&[1; 32]),
|
||||
owner: Pubkey::new(&[2; 32]),
|
||||
amount: 123,
|
||||
source: None,
|
||||
});
|
||||
assert!(account.serialize(&mut userdata).is_ok());
|
||||
assert_eq!(TokenProgram::deserialize(&userdata), Ok(account));
|
||||
|
||||
let account = TokenProgram::Token(TokenInfo {
|
||||
supply: 12345,
|
||||
decimals: 2,
|
||||
name: "A test token".to_string(),
|
||||
symbol: "TEST".to_string(),
|
||||
});
|
||||
assert!(account.serialize(&mut userdata).is_ok());
|
||||
assert_eq!(TokenProgram::deserialize(&userdata), Ok(account));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn serde_expect_fail() {
|
||||
let mut userdata = vec![0; 256];
|
||||
|
||||
// Certain TokenProgram's may not be serialized
|
||||
let account = TokenProgram::default();
|
||||
assert_eq!(account, TokenProgram::Unallocated);
|
||||
assert!(account.serialize(&mut userdata).is_err());
|
||||
assert!(account.serialize(&mut userdata).is_err());
|
||||
let account = TokenProgram::Invalid;
|
||||
assert!(account.serialize(&mut userdata).is_err());
|
||||
|
||||
// Bad deserialize userdata
|
||||
assert!(TokenProgram::deserialize(&[]).is_err());
|
||||
assert!(TokenProgram::deserialize(&[1]).is_err());
|
||||
assert!(TokenProgram::deserialize(&[1, 2]).is_err());
|
||||
assert!(TokenProgram::deserialize(&[2, 2]).is_err());
|
||||
assert!(TokenProgram::deserialize(&[3]).is_err());
|
||||
}
|
||||
|
||||
// Note: business logic tests are located in the @solana/web3.js test suite
|
||||
}
|
@ -124,7 +124,7 @@ impl WriteStage {
|
||||
}
|
||||
inc_new_counter_info!("write_stage-entries_received", num_new_entries);
|
||||
|
||||
info!("write_stage entries: {}", num_new_entries);
|
||||
debug!("write_stage entries: {}", num_new_entries);
|
||||
|
||||
let mut entries_send_total = 0;
|
||||
let mut crdt_votes_total = 0;
|
||||
@ -165,7 +165,7 @@ impl WriteStage {
|
||||
"write_stage-time_ms",
|
||||
duration_as_ms(&now.elapsed()) as usize
|
||||
);
|
||||
info!("done write_stage txs: {} time {} ms txs/s: {} entries_send_total: {} crdt_votes_total: {}",
|
||||
debug!("done write_stage txs: {} time {} ms txs/s: {} entries_send_total: {} crdt_votes_total: {}",
|
||||
num_txs, duration_as_ms(&start.elapsed()),
|
||||
num_txs as f32 / duration_as_s(&start.elapsed()),
|
||||
entries_send_total,
|
||||
|
Reference in New Issue
Block a user