Compare commits
13 Commits
Author | SHA1 | Date | |
---|---|---|---|
68ec814bf3 | |||
d7f283a00a | |||
609889cc58 | |||
83fa02ee44 | |||
d8fcb22000 | |||
6b1c90a8b5 | |||
67d72e709f | |||
f0d2870e0f | |||
02c47b48da | |||
d30a39cd38 | |||
9e57d0467e | |||
fb57d13c03 | |||
41f6e27bba |
@ -105,16 +105,16 @@ serde_cbor = "0.9.0"
|
|||||||
serde_derive = "1.0.27"
|
serde_derive = "1.0.27"
|
||||||
serde_json = "1.0.10"
|
serde_json = "1.0.10"
|
||||||
socket2 = "0.3.8"
|
socket2 = "0.3.8"
|
||||||
solana_program_interface = { path = "common" }
|
solana_program_interface = { path = "common", version="0.1.0" }
|
||||||
sys-info = "0.5.6"
|
sys-info = "0.5.6"
|
||||||
tokio = "0.1"
|
tokio = "0.1"
|
||||||
tokio-codec = "0.1"
|
tokio-codec = "0.1"
|
||||||
untrusted = "0.6.2"
|
untrusted = "0.6.2"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
noop = { path = "programs/noop" }
|
noop = { path = "programs/noop", version="0.1.0" }
|
||||||
print = { path = "programs/print" }
|
print = { path = "programs/print", version="0.1.0" }
|
||||||
move_funds = { path = "programs/move_funds" }
|
move_funds = { path = "programs/move_funds", version="0.1.0" }
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
name = "bank"
|
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"
|
- command: "ci/publish-crate.sh"
|
||||||
timeout_in_minutes: 20
|
timeout_in_minutes: 20
|
||||||
name: "publish crate [public]"
|
name: "publish crate [public]"
|
||||||
- trigger: "solana-snap"
|
- trigger: "solana-secondary"
|
||||||
branches: "!pull/*"
|
branches: "!pull/*"
|
||||||
async: true
|
async: true
|
||||||
build:
|
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)"
|
eval "$(ci/channel-info.sh)"
|
||||||
|
|
||||||
if [[ $BUILDKITE_BRANCH = "$STABLE_CHANNEL" ]]; then
|
if [[ $BUILDKITE_BRANCH = "$STABLE_CHANNEL" ]]; then
|
||||||
SNAP_CHANNEL=stable
|
CHANNEL=stable
|
||||||
elif [[ $BUILDKITE_BRANCH = "$EDGE_CHANNEL" ]]; then
|
elif [[ $BUILDKITE_BRANCH = "$EDGE_CHANNEL" ]]; then
|
||||||
SNAP_CHANNEL=edge
|
CHANNEL=edge
|
||||||
elif [[ $BUILDKITE_BRANCH = "$BETA_CHANNEL" ]]; then
|
elif [[ $BUILDKITE_BRANCH = "$BETA_CHANNEL" ]]; then
|
||||||
SNAP_CHANNEL=beta
|
CHANNEL=beta
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -z $SNAP_CHANNEL ]]; then
|
if [[ -z $CHANNEL ]]; then
|
||||||
echo Unable to determine channel to publish into, exiting.
|
echo Unable to determine channel to publish into, exiting.
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
@ -51,11 +51,11 @@ if [[ ! -x /usr/bin/multilog ]]; then
|
|||||||
sudo apt-get install -y daemontools
|
sudo apt-get install -y daemontools
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo --- build: $SNAP_CHANNEL channel
|
echo --- build: $CHANNEL channel
|
||||||
snapcraft
|
snapcraft
|
||||||
|
|
||||||
source ci/upload_ci_artifact.sh
|
source ci/upload_ci_artifact.sh
|
||||||
upload_ci_artifact solana_*.snap
|
upload_ci_artifact solana_*.snap
|
||||||
|
|
||||||
echo --- publish: $SNAP_CHANNEL channel
|
echo --- publish: $CHANNEL channel
|
||||||
$DRYRUN snapcraft push solana_*.snap --release $SNAP_CHANNEL
|
$DRYRUN snapcraft push solana_*.snap --release $CHANNEL
|
||||||
|
@ -31,11 +31,7 @@ __cloud_FindInstances() {
|
|||||||
|
|
||||||
declare name zone publicIp privateIp status
|
declare name zone publicIp privateIp status
|
||||||
while read -r name publicIp privateIp status; do
|
while read -r name publicIp privateIp status; do
|
||||||
if [[ $status != RUNNING ]]; then
|
printf "%-30s | publicIp=%-16s privateIp=%s staus=%s\n" "$name" "$publicIp" "$privateIp" "$status"
|
||||||
echo "Warning: $name is not RUNNING, ignoring it."
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
printf "%-30s | publicIp=%-16s privateIp=%s\n" "$name" "$publicIp" "$privateIp"
|
|
||||||
|
|
||||||
instances+=("$name:$publicIp:$privateIp")
|
instances+=("$name:$publicIp:$privateIp")
|
||||||
done < <(gcloud compute instances list \
|
done < <(gcloud compute instances list \
|
||||||
|
@ -13,8 +13,8 @@ sysctl -w kernel.sysrq=$(( $(cat /proc/sys/kernel/sysrq) | 64 ))
|
|||||||
if command -v earlyoom; then
|
if command -v earlyoom; then
|
||||||
systemctl status earlyoom
|
systemctl status earlyoom
|
||||||
else
|
else
|
||||||
wget http://ftp.us.debian.org/debian/pool/main/e/earlyoom/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-2_amd64.deb
|
apt install --quiet --yes ./earlyoom_1.1-*_amd64.deb
|
||||||
|
|
||||||
cat > earlyoom <<OOM
|
cat > earlyoom <<OOM
|
||||||
# use the kernel OOM killer, trigger at 20% available RAM,
|
# use the kernel OOM killer, trigger at 20% available RAM,
|
||||||
|
@ -31,6 +31,7 @@ use system_transaction::SystemTransaction;
|
|||||||
use tictactoe_dashboard_program::TicTacToeDashboardProgram;
|
use tictactoe_dashboard_program::TicTacToeDashboardProgram;
|
||||||
use tictactoe_program::TicTacToeProgram;
|
use tictactoe_program::TicTacToeProgram;
|
||||||
use timing::{duration_as_us, timestamp};
|
use timing::{duration_as_us, timestamp};
|
||||||
|
use token_program::TokenProgram;
|
||||||
use transaction::Transaction;
|
use transaction::Transaction;
|
||||||
use window::WINDOW_SIZE;
|
use window::WINDOW_SIZE;
|
||||||
|
|
||||||
@ -410,6 +411,10 @@ impl Bank {
|
|||||||
if TicTacToeDashboardProgram::process_transaction(&tx, accounts).is_err() {
|
if TicTacToeDashboardProgram::process_transaction(&tx, accounts).is_err() {
|
||||||
return Err(BankError::ProgramRuntimeError);
|
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 if self.loaded_contract(&tx, accounts) {
|
||||||
} else {
|
} else {
|
||||||
return Err(BankError::UnknownContractId);
|
return Err(BankError::UnknownContractId);
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
|
|
||||||
use bank::Bank;
|
use bank::Bank;
|
||||||
use bincode::deserialize;
|
use bincode::deserialize;
|
||||||
use budget_transaction::BudgetTransaction;
|
|
||||||
use counter::Counter;
|
use counter::Counter;
|
||||||
use entry::Entry;
|
use entry::Entry;
|
||||||
use log::Level;
|
use log::Level;
|
||||||
@ -224,7 +223,7 @@ impl BankingStage {
|
|||||||
.zip(vers)
|
.zip(vers)
|
||||||
.filter_map(|(tx, ver)| match tx {
|
.filter_map(|(tx, ver)| match tx {
|
||||||
None => None,
|
None => None,
|
||||||
Some((tx, _addr)) => if tx.verify_plan() && ver != 0 {
|
Some((tx, _addr)) => if ver != 0 {
|
||||||
Some(tx)
|
Some(tx)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
@ -526,7 +526,7 @@ impl Crdt {
|
|||||||
received_index: u64,
|
received_index: u64,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
if broadcast_table.is_empty() {
|
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);
|
inc_new_counter_info!("crdt-broadcast-not_enough_peers_error", 1);
|
||||||
Err(CrdtError::NoPeers)?;
|
Err(CrdtError::NoPeers)?;
|
||||||
}
|
}
|
||||||
|
@ -63,6 +63,7 @@ pub mod thin_client;
|
|||||||
pub mod tictactoe_dashboard_program;
|
pub mod tictactoe_dashboard_program;
|
||||||
pub mod tictactoe_program;
|
pub mod tictactoe_program;
|
||||||
pub mod timing;
|
pub mod timing;
|
||||||
|
pub mod token_program;
|
||||||
pub mod tpu;
|
pub mod tpu;
|
||||||
pub mod transaction;
|
pub mod transaction;
|
||||||
pub mod tvu;
|
pub mod tvu;
|
||||||
|
@ -22,14 +22,14 @@ impl TicTacToeDashboardProgram {
|
|||||||
if input.len() < 2 {
|
if input.len() < 2 {
|
||||||
Err(Error::InvalidUserdata)?;
|
Err(Error::InvalidUserdata)?;
|
||||||
}
|
}
|
||||||
let len = input[0] as usize + (0xFF * input[1] as usize);
|
let len = input[0] as usize + (0x100 * input[1] as usize);
|
||||||
if len == 0 {
|
if len == 0 {
|
||||||
Ok(TicTacToeDashboardProgram::default())
|
Ok(TicTacToeDashboardProgram::default())
|
||||||
} else if input.len() < len + 2 {
|
} else if input.len() < len + 2 {
|
||||||
Err(Error::InvalidUserdata)
|
Err(Error::InvalidUserdata)
|
||||||
} else {
|
} else {
|
||||||
serde_cbor::from_slice(&input[2..(2 + len)]).map_err(|err| {
|
serde_cbor::from_slice(&input[2..(2 + len)]).map_err(|err| {
|
||||||
error!("Unable to deserialize game: {:?}", err);
|
error!("Unable to deserialize: {:?}", err);
|
||||||
Error::InvalidUserdata
|
Error::InvalidUserdata
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -137,13 +137,16 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
pub fn serde() {
|
pub fn serde() {
|
||||||
let mut dashboard1 = TicTacToeDashboardProgram::default();
|
let mut dashboard1 = TicTacToeDashboardProgram::default();
|
||||||
dashboard1.total = 123;
|
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; 256];
|
let mut userdata = vec![0xff; 512];
|
||||||
dashboard1.serialize(&mut userdata).unwrap();
|
dashboard1.serialize(&mut userdata).unwrap();
|
||||||
|
|
||||||
let dashboard2 = TicTacToeDashboardProgram::deserialize(&userdata).unwrap();
|
let dashboard2 = TicTacToeDashboardProgram::deserialize(&userdata).unwrap();
|
||||||
|
|
||||||
assert_eq!(dashboard1, dashboard2);
|
assert_eq!(dashboard1, dashboard2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@ pub enum Error {
|
|||||||
InvalidArguments,
|
InvalidArguments,
|
||||||
InvalidMove,
|
InvalidMove,
|
||||||
InvalidUserdata,
|
InvalidUserdata,
|
||||||
|
InvalidTimestamp,
|
||||||
NoGame,
|
NoGame,
|
||||||
NotYourTurn,
|
NotYourTurn,
|
||||||
PlayerNotFound,
|
PlayerNotFound,
|
||||||
@ -74,7 +75,7 @@ impl Game {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub fn new(player_x: Pubkey, player_o: Pubkey) -> Game {
|
pub fn new(player_x: Pubkey, player_o: Pubkey) -> Game {
|
||||||
let mut game = Game::create(&player_x);
|
let mut game = Game::create(&player_x);
|
||||||
game.join(player_o, 0).unwrap();
|
game.join(player_o, 1).unwrap();
|
||||||
game
|
game
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,8 +83,13 @@ impl Game {
|
|||||||
if self.state == State::Waiting {
|
if self.state == State::Waiting {
|
||||||
self.player_o = Some(player_o);
|
self.player_o = Some(player_o);
|
||||||
self.state = State::XMove;
|
self.state = State::XMove;
|
||||||
|
|
||||||
|
if timestamp <= self.keep_alive[1] {
|
||||||
|
Err(Error::InvalidTimestamp)
|
||||||
|
} else {
|
||||||
self.keep_alive[1] = timestamp;
|
self.keep_alive[1] = timestamp;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(Error::GameInProgress)
|
Err(Error::GameInProgress)
|
||||||
}
|
}
|
||||||
@ -143,13 +149,25 @@ impl Game {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn keep_alive(self: &mut Game, player: Pubkey, timestamp: i64) -> Result<()> {
|
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 player == self.player_x {
|
||||||
|
if timestamp <= self.keep_alive[0] {
|
||||||
|
Err(Error::InvalidTimestamp)?;
|
||||||
|
}
|
||||||
self.keep_alive[0] = timestamp;
|
self.keep_alive[0] = timestamp;
|
||||||
} else if Some(player) == self.player_o {
|
} else if Some(player) == self.player_o {
|
||||||
|
if timestamp <= self.keep_alive[1] {
|
||||||
|
Err(Error::InvalidTimestamp)?;
|
||||||
|
}
|
||||||
self.keep_alive[1] = timestamp;
|
self.keep_alive[1] = timestamp;
|
||||||
} else {
|
} else {
|
||||||
Err(Error::PlayerNotFound)?;
|
Err(Error::PlayerNotFound)?;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
// Ignore keep_alive when game is no longer in progress
|
||||||
|
State::XWon | State::OWon | State::Draw => {}
|
||||||
|
};
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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);
|
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 entries_send_total = 0;
|
||||||
let mut crdt_votes_total = 0;
|
let mut crdt_votes_total = 0;
|
||||||
@ -165,7 +165,7 @@ impl WriteStage {
|
|||||||
"write_stage-time_ms",
|
"write_stage-time_ms",
|
||||||
duration_as_ms(&now.elapsed()) as usize
|
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, duration_as_ms(&start.elapsed()),
|
||||||
num_txs as f32 / duration_as_s(&start.elapsed()),
|
num_txs as f32 / duration_as_s(&start.elapsed()),
|
||||||
entries_send_total,
|
entries_send_total,
|
||||||
|
Reference in New Issue
Block a user