Skip to content

Ava types plugin logic #36

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

Closed
wants to merge 52 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
d0ebd32
mini-kvvm: add initial block implementation
hexfusion May 25, 2022
8a68bea
mini-kvvm: add state
hexfusion May 25, 2022
0450f72
mini-kvvm: add engine
hexfusion May 25, 2022
0a7ec76
kvvm: add ChainVMInterior
hexfusion May 25, 2022
7e72128
mimi-kvvm/plugin: add reflection service
hexfusion May 25, 2022
d9cf4f5
mini-kvvm/main: add plugin support
hexfusion May 25, 2022
b556a8d
mini-kvvm/genesis: add verify
hexfusion May 25, 2022
10c1efc
mini-kvvm: update lib
hexfusion May 25, 2022
8b986d9
mini-kvvm: bump deps
hexfusion May 25, 2022
0efd8be
tests: add e2e testing for custom vm
hexfusion May 25, 2022
9d8d957
.github/workflows: verify version during release
hexfusion May 26, 2022
d2ec7c6
mini-kvvm: dep nits
hexfusion May 26, 2022
07d23ca
nits
hexfusion May 26, 2022
3034293
*: remove duplicate method
hexfusion May 26, 2022
34f17c6
mini-kvvm: remove duplicate block put
hexfusion May 26, 2022
cc8e5b5
block: ensure epoc 0
hexfusion May 26, 2022
1d43451
mini-kvvm: clarify usage of std::io::Result vs std::result::Result
hexfusion Jun 3, 2022
b4a5b5c
refactoring
hexfusion Jun 4, 2022
8e3b757
mini-kvvm/block: refactor initalize and verify
hexfusion Jun 4, 2022
b3cba12
mini-kvvm/kvvm: refactor and cleanup
hexfusion Jun 4, 2022
43f70e6
mini-kvvm: add initial block implementation
hexfusion May 25, 2022
fd91f7d
mini-kvvm: add state
hexfusion May 25, 2022
33121ad
mini-kvvm: add engine
hexfusion May 25, 2022
0f27cfc
kvvm: add ChainVMInterior
hexfusion May 25, 2022
9d547c2
mimi-kvvm/plugin: add reflection service
hexfusion May 25, 2022
be3a43b
mini-kvvm/main: add plugin support
hexfusion May 25, 2022
8581fac
mini-kvvm/genesis: add verify
hexfusion May 25, 2022
0279545
mini-kvvm: update lib
hexfusion May 25, 2022
9cfe4bc
mini-kvvm: bump deps
hexfusion May 25, 2022
d259ac9
tests: add e2e testing for custom vm
hexfusion May 25, 2022
55a504c
.github/workflows: verify version during release
hexfusion May 26, 2022
e57ea0c
mini-kvvm: dep nits
hexfusion May 26, 2022
4722764
nits
hexfusion May 26, 2022
9b2cadc
*: remove duplicate method
hexfusion May 26, 2022
7abf53b
mini-kvvm: remove duplicate block put
hexfusion May 26, 2022
98baaa1
block: ensure epoc 0
hexfusion May 26, 2022
9ff0799
mini-kvvm: clarify usage of std::io::Result vs std::result::Result
hexfusion Jun 3, 2022
e77e0e9
refactoring
hexfusion Jun 4, 2022
825f702
mini-kvvm/block: refactor initalize and verify
hexfusion Jun 4, 2022
18ac1d8
mini-kvvm/kvvm: refactor and cleanup
hexfusion Jun 4, 2022
39a6562
mini-kvvm: use NodeId vs ShortId
hexfusion Jun 6, 2022
d594e6b
nits
hexfusion Jun 7, 2022
4e4d1ad
mini-kvvm: bump avalanche-types 0.0.9
hexfusion Jun 8, 2022
370df76
mini-kvvm/state: simplify has_last_accepted_block
hexfusion Jun 8, 2022
d97e4bc
nits
hexfusion Jun 8, 2022
4f6ca81
mini-kvvm: update to protocol version 15
hexfusion Jun 11, 2022
20c376c
mini-kvvm: move plugin to avalanche-types
hexfusion Jun 11, 2022
4dd39a8
.github/workflows: bump e2e testing against 1.7.13
hexfusion Jun 11, 2022
5c751f3
updated documentation on engine.rs
morrisettathena Jun 14, 2022
25cc3e4
updated documentation and merged with newer version
morrisettathena Jun 14, 2022
6df3fda
updated documentation and merged with newer version
morrisettathena Jun 14, 2022
35d82df
produces vm binary compatible with avalanchego
morrisettathena Aug 8, 2022
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
4 changes: 2 additions & 2 deletions .github/workflows/test-and-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ jobs:
with:
cache-on-failure: true
- name: Run e2e tests
run: scripts/tests.e2e.sh 1.7.10
run: scripts/tests.e2e.sh 1.7.13

release:
name: Release ${{ matrix.job.target }} (${{ matrix.job.os }})
Expand Down Expand Up @@ -232,7 +232,7 @@ jobs:
run: |
if [ "$PLATFORM_NAME" == "linux" ]; then

./target/${TARGET}/release/mini-kvvm
./target/${TARGET}/release/mini-kvvm --version
cp ./target/${TARGET}/release/mini-kvvm mini-kvvm.${TARGET}
echo "::set-output name=file_name_mini-kvvm::mini-kvvm.${TARGET}"
tar -czvf mini-kvvm_${TARGET}.tar.gz -C ./target/${TARGET}/release mini-kvvm
Expand Down
33 changes: 23 additions & 10 deletions mini-kvvm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name = "mini-kvvm"
version = "0.0.0"
edition = "2021"
rust-version = "1.60"
rust-version = "1.61"
publish = false
description = "Mini key-value store VM for Avalanche in Rust"
license = "BSD-3-Clause"
Expand All @@ -14,15 +14,28 @@ name = "mini-kvvm"
path = "src/bin/mini-kvvm/main.rs"

[dependencies]
avalanche-proto = { path = "../crates/avalanche-proto" }
clap = { version = "3.1.18", features = ["cargo", "derive"] }
async-trait = "0.1.53"
avalanche-proto = { version = "0.15.1" }
avalanche-types = "0.0.27" #NOTE avalanche-types must be updated from branch ava-types-plugin-logic for this to work
avalanche-utils = { version="0.0.3", features = ["rfc3339"] }
bytes = "1.1.0"
chrono = "0.4.19"
clap = { version = "3.1.8", features = ["cargo", "derive"] }
env_logger = "0.9.0"
log = "0.4.17"
prost = "0.10.3"
serde = { version = "1.0.137", features = ["derive"] }
serde_json = "1.0.81"
serde_yaml = "0.8.24"
tokio = { version = "1.18.2", features = ["fs", "rt-multi-thread"] }
tokio-stream = { version = "0.1.8", features = ["net"] }
hmac-sha256 = "1.1"
jsonrpc-core = "18.0"
jsonrpc-derive = "18.0"
log = "0.4.16"
num-traits = "0.2.15"
num-derive = "0.3"
prost = "0.10.0"
semver = "1.0.9"
serde = { version = "1.0.136", features = ["derive"] }
serde_json = "1.0.79"
serde_yaml = "0.8.23"
time = { version = "0.3.9", features = ["formatting", "parsing"]}
tokio = { version = "1.19.1", features = ["fs", "rt-multi-thread"] }
tokio-stream = { version = "0.1.9", features = ["net"] }
tonic = { version = "0.7", features = ["compression"] }
tonic-health = "0.6"
tonic-reflection = "0.4.0"
26 changes: 16 additions & 10 deletions mini-kvvm/src/bin/mini-kvvm/main.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
use std::io;
use std::{io::Result, sync::Arc};

use clap::{crate_version, Arg, Command};
use log::info;

use mini_kvvm::genesis;
use mini_kvvm::{genesis, kvvm};
use tokio::sync::RwLock;

pub const APP_NAME: &str = "mini-kvvm-rs";

fn main() {
#[tokio::main]
async fn main() -> Result<()> {
let matches = Command::new(APP_NAME)
.version(crate_version!())
.about("Mini key-value VM for Avalanche in Rust")
Expand Down Expand Up @@ -38,11 +39,18 @@ fn main() {
let msg = sub_matches.value_of("WELCOME_MESSAGE").unwrap_or("");
let p = sub_matches.value_of("GENESIS_FILE_PATH").unwrap_or("");
execute_genesis(author, msg, p).unwrap();
return;
return Ok(());
}

info!("starting mini-kvvm-rs");
// TODO
let mini_kvvm = kvvm::ChainVm::new(Arc::new(RwLock::new(kvvm::ChainVmInterior::default())));
let rpcchain = avalanche_types::rpcchainvm::vm::server::Server::new(mini_kvvm);

avalanche_types::rpcchainvm::plugin::serve(rpcchain)
.await
.expect("failed to start server");

Ok(())
}

pub fn command_genesis() -> Command<'static> {
Expand Down Expand Up @@ -79,12 +87,10 @@ pub fn command_genesis() -> Command<'static> {
)
}

pub fn execute_genesis(author: &str, msg: &str, p: &str) -> io::Result<()> {
pub fn execute_genesis(author: &str, msg: &str, p: &str) -> Result<()> {
let g = genesis::Genesis {
author: String::from(author),
welcome_message: String::from(msg),
};
g.sync(p)?;

Ok(())
g.sync(p)
}
253 changes: 253 additions & 0 deletions mini-kvvm/src/block.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
use std::{
cmp::Ordering,
io::{Error, ErrorKind, Result},
};

use avalanche_types::{
choices::status::Status,
ids::{must_deserialize_id, Id},
rpcchainvm,
};
use avalanche_utils::rfc3339;
use chrono::{DateTime, NaiveDateTime, Utc};
use hmac_sha256::Hash;
use serde::{Deserialize, Serialize};

use crate::kvvm::ChainVm;

pub const DATA_LEN: usize = 32;

impl Block {
pub fn new(
parent: Id,
height: u64,
data: Vec<u8>,
timestamp: DateTime<Utc>,
status: Status,
) -> Self {
Self {
parent,
height,
timestamp,
data,
status,
id: Id::empty(),
bytes: Vec::default(),
vm: None,
}
}
}

pub trait MiniKvvmBlock: rpcchainvm::concensus::snowman::Block + Serialize {
fn data(&self) -> &[u8];
fn initialize(&mut self, vm: ChainVm) -> Result<Id>;
fn set_status(&mut self, status: Status);
}

// TODO remove
// Default is only used as a placeholder for unimplemented block logic
impl Default for Block {
fn default() -> Self {
Self {
id: Id::empty(),
parent: Id::empty(),
timestamp: DateTime::<Utc>::from_utc(NaiveDateTime::from_timestamp(0, 0), Utc),
bytes: Vec::default(),
height: 0,
status: Status::Unknown("".to_string()),
data: Vec::default(),
vm: None,
}
}
}

/// snow/consensus/snowman/Block
/// ref. https://pkg.go.dev/github.com/ava-labs/avalanchego/snow/consensus/snowman#Block
#[derive(Serialize, Clone, Deserialize)]
pub struct Block {
#[serde(deserialize_with = "must_deserialize_id")]
pub parent: Id,
pub status: Status,
height: u64,
#[serde(with = "rfc3339::serde_format")]
timestamp: DateTime<Utc>,
data: Vec<u8>,

// generated not serialized
#[serde(skip)]
id: Id,
// generated not serialized
#[serde(skip)]
bytes: Vec<u8>,
#[serde(skip)]
vm: Option<ChainVm>,
}

#[tonic::async_trait]
impl rpcchainvm::concensus::snowman::Decidable for Block {
/// id returns the ID of this block
async fn id(&self) -> Id {
self.id
}

/// status returns the status of this block
async fn status(&self) -> Status {
self.status.clone()
}

/// Accepts this element.
async fn accept(&mut self) -> Result<()> {
let vm = self.vm.clone();
let vm = vm.ok_or(Error::new(ErrorKind::Other, "no vm associated with block"))?;
let mut inner = vm.inner.write().await;

self.status = Status::Accepted;

// add newly accepted block to state
inner
.state
.put_block(self.clone(), vm.clone())
.await
.map_err(|e| Error::new(ErrorKind::Other, format!("failed to put block: {:?}", e)))?;

// set last accepted block to this block id
inner
.state
.set_last_accepted_block_id(&self.id)
.await
.map_err(|e| Error::new(ErrorKind::Other, format!("failed to put block: {:?}", e)))?;

// remove from verified blocks
inner.verified_blocks.remove(&self.id);
Ok(())
}

/// Rejects this element.
async fn reject(&mut self) -> Result<()> {
let vm = self.vm.clone();
let vm = vm.ok_or(Error::new(ErrorKind::Other, "no vm associated with block"))?;
let mut inner = vm.inner.write().await;

self.status = Status::Rejected;

// add newly rejected block to state
inner
.state
.put_block(self.clone(), vm.clone())
.await
.map_err(|e| Error::new(ErrorKind::Other, format!("failed to put block: {:?}", e)))?;

// remove from verified, as it is rejected
inner.verified_blocks.remove(&self.id);
Ok(())
}
}

#[tonic::async_trait]
impl rpcchainvm::concensus::snowman::Block for Block {
/// bytes returns the binary representation of this block
async fn bytes(&self) -> &[u8] {
&self.bytes
}

/// height returns this block's height. The genesis block has height 0.
async fn height(&self) -> u64 {
self.height
}

async fn timestamp(&self) -> u64 {
self.timestamp.timestamp() as u64
}

async fn parent(&self) -> Id {
self.parent
}

/// verify ensures that the state of the block is expected.
async fn verify(&self) -> Result<()> {
let vm = self
.vm
.clone()
.ok_or(Error::new(ErrorKind::Other, "no reference to vm"))?;

let vm = vm.inner.read().await;

match vm.state.get_block(self.parent).await? {
Some(parent_block) => {
// Ensure block height comes right after its parent's height
if parent_block.height().await + 1 != self.height {
return Err(Error::new(
ErrorKind::InvalidData,
"failed to verify block invalid height",
));
}
// Ensure block timestamp is after its parent's timestamp.
if self.timestamp().await.cmp(&parent_block.timestamp().await) == Ordering::Less {
return Err(Error::new(
ErrorKind::InvalidData,
format!(
"block timestamp: {} is after parents: {}",
self.timestamp().await,
parent_block.timestamp().await
),
));
}
Ok(())
}
None => Err(Error::new(
ErrorKind::NotFound,
"failed to verify block parent not found",
)),
}
}
}

impl MiniKvvmBlock for Block {
/// data returns the block payload.
fn data(&self) -> &[u8] {
&self.data
}

fn set_status(&mut self, status: Status) {
self.status = status;
}

/// initialize populates the generated fields (id, bytes) of the the block and
/// returns the generated id.
fn initialize(&mut self, vm: ChainVm) -> Result<Id> {
if self.id.is_empty() {
match serde_json::to_vec(&self) {
// Populate generated fields
Ok(block_bytes) => {
let block_data = block_bytes.as_slice();
let block_id = to_block_id(&block_data);
self.id = block_id;
self.bytes = block_bytes;
self.vm = Some(vm);
return Ok(self.id);
}
Err(error) => {
return Err(Error::new(ErrorKind::NotFound, error));
}
}
}
Ok(self.id)
}
}

fn to_block_id(bytes: &[u8]) -> Id {
new_id(Hash::hash(bytes))
}

fn new_id(bytes: [u8; DATA_LEN]) -> Id {
Id::from_slice(&bytes)
}

#[tokio::test]
async fn test_serialization_round_trip() {
use rpcchainvm::concensus::snowman::Block as _; //Bring the block trait into scope for [.parent()]
let block = Block::default();
let writer = serde_json::to_vec(&block).unwrap();
let value: Block = serde_json::from_slice(&writer).unwrap();
assert_eq!(block.parent().await, value.parent().await);
}
16 changes: 16 additions & 0 deletions mini-kvvm/src/genesis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,22 @@ impl Genesis {
Ok(resp)
}

pub fn verify(&self) -> Result<(), Error> {
if self.author.is_empty() {
return Err(Error::new(
ErrorKind::InvalidData,
format!("invalid author"),
));
}
if self.welcome_message.is_empty() {
return Err(Error::new(
ErrorKind::InvalidData,
format!("invalid welcome_message"),
));
}
Ok(())
}

pub fn sync(&self, file_path: &str) -> io::Result<()> {
info!("syncing genesis to '{}'", file_path);
let path = Path::new(file_path);
Expand Down
Loading