Added missing files from previous commit. Added Android-studio-material ui.

This commit is contained in:
Adrian Tiberius 2015-05-14 16:22:19 +02:00
parent 0b5f6f9c02
commit 856cea2867
76 changed files with 3862 additions and 209 deletions

View File

@ -0,0 +1,68 @@
package org.ethereum.ethereum_android;
import org.ethereum.core.AccountState;
import org.ethereum.core.Denomination;
import org.ethereum.crypto.HashUtil;
import org.spongycastle.util.Arrays;
import org.spongycastle.util.encoders.Hex;
import java.util.List;
/**
* Created by Adrian Tiberius on 06.05.2015.
*/
public class AccountsDataAdapter {
List<DataClass> data;
final String[] columns = new String[]{"Account", "Balance", "Is Contract"};
public AccountsDataAdapter(List<DataClass> data) {
this.data = data;
}
public void addDataPiece(DataClass d) {
data.add(d);
//this.fireTableRowsInserted(Math.min(data.size() - 2, 0), data.size() - 1);
}
public int getRowCount() {
return data.size();
}
public int getColumnCount() {
return 3;
}
public String getColumnName(int column) {
return columns[column];
}
public boolean isCellEditable(int row, int column) { // custom isCellEditable function
return column == 0 ? true : false;
}
public Object getValueAt(int rowIndex, int columnIndex) {
if (columnIndex == 0) {
return Hex.toHexString(data.get(rowIndex).address);
} else if (columnIndex == 1) {
if (data.get(rowIndex).accountState != null) {
return Denomination.toFriendlyString(data.get(rowIndex).accountState.getBalance());
}
return "---";
} else {
if (data.get(rowIndex).accountState != null) {
if (!Arrays.areEqual(data.get(rowIndex).accountState.getCodeHash(), HashUtil.EMPTY_DATA_HASH))
return "Yes";
}
return "No";
}
}
public static class DataClass {
public byte[] address;
public AccountState accountState;
}
}

View File

@ -0,0 +1,11 @@
package org.ethereum.ethereum_android;
import org.ethereum.core.AccountState;
/**
* Created by userica on 06.05.2015.
*/
public class DataClass {
public byte[] address;
public AccountState accountState;
}

View File

@ -0,0 +1,73 @@
package org.ethereum.ethereum_android;
import org.ethereum.config.SystemProperties;
import org.ethereum.core.AccountState;
import org.ethereum.facade.Ethereum;
import org.ethereum.EthereumFactory;
import org.ethereum.facade.Repository;
import org.ethereum.listener.EthereumListenerAdapter;
import java.util.Set;
public class EthereumManager {
public static Ethereum ethereum = null;
public static AccountsDataAdapter adapter = null;
public static String log = "";
public EthereumManager(android.content.Context androidContext) {
ethereum = EthereumFactory.getEthereum(androidContext);
this.addListener();
}
public void connect() {
ethereum.connect(SystemProperties.CONFIG.activePeerIP(),
SystemProperties.CONFIG.activePeerPort(),
SystemProperties.CONFIG.activePeerNodeid());
}
public void loadAccounts() {
Repository repository = ethereum.getRepository();
Set<byte[]> keys = repository.getAccountsKeys();
for (byte[] key : keys) {
AccountsDataAdapter.DataClass dc = new AccountsDataAdapter.DataClass();
dc.address = key;
AccountState state = repository.getAccountState(dc.address);
dc.accountState = state;
adapter.addDataPiece(dc);
}
}
public void startPeerDiscovery() {
ethereum.startPeerDiscovery();
}
public void addListener() {
ethereum.addListener(new EthereumListenerAdapter() {
@Override
public void trace(final String output) {
log += output;
log += "\n\n";
}
});
}
public String getLog() {
String logMessages = EthereumManager.log;
EthereumManager.log = "";
return logMessages;
}
}

View File

@ -2,8 +2,10 @@ package org.ethereum.ethereum_android;
import android.os.AsyncTask;
import android.os.StrictMode;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.support.v7.widget.Toolbar;
import android.text.method.ScrollingMovementMethod;
import android.util.Log;
import android.view.Menu;
@ -12,11 +14,12 @@ import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.view.View.OnClickListener;
import android.widget.Toast;
import org.springframework.context.ApplicationContext;
public class MainActivity extends ActionBarActivity implements OnClickListener {
public class MainActivity extends ActionBarActivity implements OnClickListener, NavigationDrawerCallbacks {
public static ApplicationContext context = null;
private static final String TAG = "MyActivity";
@ -26,24 +29,28 @@ public class MainActivity extends ActionBarActivity implements OnClickListener {
private Button walletButton;
public EthereumManager ethereumManager = null;
private NavigationDrawerFragment mNavigationDrawerFragment;
private Toolbar mToolbar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mToolbar = (Toolbar) findViewById(R.id.toolbar_actionbar);
setSupportActionBar(mToolbar);
mNavigationDrawerFragment = (NavigationDrawerFragment)
getFragmentManager().findFragmentById(R.id.fragment_drawer);
// Set up the drawer.
mNavigationDrawerFragment.setup(R.id.fragment_drawer, (DrawerLayout) findViewById(R.id.drawer), mToolbar);
text1 = (TextView) findViewById(R.id.text1);
text1.setMovementMethod(new ScrollingMovementMethod());
consoleButton = (Button) findViewById(R.id.consoleButton);
consoleButton.setOnClickListener(this);
walletButton = (Button) findViewById(R.id.walletButton);
walletButton.setOnClickListener(this);
StrictMode.enableDefaults();
//context = RoboSpring.getContext("applicationContext.xml");//new ClassPathXmlApplicationContext("applicationContext.xml"/*, clazz*/);
//RoboSpring.autowire(this);
System.setProperty("sun.arch.data.model", "32");
System.setProperty("leveldb.mmap", "false");
new PostTask().execute(getApplicationContext());
@ -75,26 +82,41 @@ public class MainActivity extends ActionBarActivity implements OnClickListener {
public void onClick(View v) {
switch (v.getId()) {
/*
case R.id.consoleButton: {
// do something for button 1 click
break;
}
case R.id.walletButton: {
// do something for button 2 click
break;
}
*/
//.... etc
}
}
@Override
public void onNavigationDrawerItemSelected(int position) {
// update the main content by replacing fragments
Toast.makeText(this, "Menu item selected -> " + position, Toast.LENGTH_SHORT).show();
}
@Override
public void onBackPressed() {
if (mNavigationDrawerFragment.isDrawerOpen())
mNavigationDrawerFragment.closeDrawer();
else
super.onBackPressed();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
if (!mNavigationDrawerFragment.isDrawerOpen()) {
// Only show items in the action bar relevant to this screen
// if the drawer is not showing. Otherwise, let the drawer
// decide what to show in the action bar.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
return super.onCreateOptionsMenu(menu);
}
@Override
@ -132,14 +154,14 @@ public class MainActivity extends ActionBarActivity implements OnClickListener {
Log.v(TAG, "333");
while(true) {
try {
Thread.sleep(1000);
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (quit == 1) {
return "All Done!";
}
publishProgress(1111);
//publishProgress(1111);
}
}

View File

@ -0,0 +1,88 @@
package org.ethereum.ethereum_android;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import java.util.List;
public class NavigationDrawerAdapter extends RecyclerView.Adapter<NavigationDrawerAdapter.ViewHolder> {
private List<NavigationItem> mData;
private NavigationDrawerCallbacks mNavigationDrawerCallbacks;
private View mSelectedView;
private int mSelectedPosition;
public NavigationDrawerAdapter(List<NavigationItem> data) {
mData = data;
}
public NavigationDrawerCallbacks getNavigationDrawerCallbacks() {
return mNavigationDrawerCallbacks;
}
public void setNavigationDrawerCallbacks(NavigationDrawerCallbacks navigationDrawerCallbacks) {
mNavigationDrawerCallbacks = navigationDrawerCallbacks;
}
@Override
public NavigationDrawerAdapter.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.drawer_row, viewGroup, false);
final ViewHolder viewHolder = new ViewHolder(v);
viewHolder.itemView.setClickable(true);
viewHolder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mSelectedView != null) {
mSelectedView.setSelected(false);
}
mSelectedPosition = viewHolder.getAdapterPosition();
v.setSelected(true);
mSelectedView = v;
if (mNavigationDrawerCallbacks != null)
mNavigationDrawerCallbacks.onNavigationDrawerItemSelected(viewHolder.getAdapterPosition());
}
}
);
viewHolder.itemView.setBackgroundResource(R.drawable.row_selector);
return viewHolder;
}
@Override
public void onBindViewHolder(NavigationDrawerAdapter.ViewHolder viewHolder, int i) {
viewHolder.textView.setText(mData.get(i).getText());
viewHolder.textView.setCompoundDrawablesWithIntrinsicBounds(mData.get(i).getDrawable(), null, null, null);
if (mSelectedPosition == i) {
if (mSelectedView != null) {
mSelectedView.setSelected(false);
}
mSelectedPosition = i;
mSelectedView = viewHolder.itemView;
mSelectedView.setSelected(true);
}
}
public void selectPosition(int position) {
mSelectedPosition = position;
notifyItemChanged(position);
}
@Override
public int getItemCount() {
return mData != null ? mData.size() : 0;
}
public static class ViewHolder extends RecyclerView.ViewHolder {
public TextView textView;
public ViewHolder(View itemView) {
super(itemView);
textView = (TextView) itemView.findViewById(R.id.item_name);
}
}
}

View File

@ -0,0 +1,5 @@
package org.ethereum.ethereum_android;
public interface NavigationDrawerCallbacks {
void onNavigationDrawerItemSelected(int position);
}

View File

@ -0,0 +1,219 @@
package org.ethereum.ethereum_android;
import android.app.Activity;
import android.app.Fragment;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBarDrawerToggle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import java.util.ArrayList;
import java.util.List;
/**
* Fragment used for managing interactions for and presentation of a navigation drawer.
* See the <a href="https://developer.android.com/design/patterns/navigation-drawer.html#Interaction">
* design guidelines</a> for a complete explanation of the behaviors implemented here.
*/
public class NavigationDrawerFragment extends Fragment implements NavigationDrawerCallbacks {
/**
* Remember the position of the selected item.
*/
private static final String STATE_SELECTED_POSITION = "selected_navigation_drawer_position";
/**
* Per the design guidelines, you should show the drawer on launch until the user manually
* expands it. This shared preference tracks this.
*/
private static final String PREF_USER_LEARNED_DRAWER = "navigation_drawer_learned";
/**
* A pointer to the current callbacks instance (the Activity).
*/
private NavigationDrawerCallbacks mCallbacks;
/**
* Helper component that ties the action bar to the navigation drawer.
*/
private ActionBarDrawerToggle mActionBarDrawerToggle;
private DrawerLayout mDrawerLayout;
private RecyclerView mDrawerList;
private View mFragmentContainerView;
private int mCurrentSelectedPosition = 0;
private boolean mFromSavedInstanceState;
private boolean mUserLearnedDrawer;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Read in the flag indicating whether or not the user has demonstrated awareness of the
// drawer. See PREF_USER_LEARNED_DRAWER for details.
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getActivity());
mUserLearnedDrawer = sp.getBoolean(PREF_USER_LEARNED_DRAWER, false);
if (savedInstanceState != null) {
mCurrentSelectedPosition = savedInstanceState.getInt(STATE_SELECTED_POSITION);
mFromSavedInstanceState = true;
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_navigation_drawer, container, false);
mDrawerList = (RecyclerView) view.findViewById(R.id.drawerList);
LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity());
layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
mDrawerList.setLayoutManager(layoutManager);
mDrawerList.setHasFixedSize(true);
final List<NavigationItem> navigationItems = getMenu();
NavigationDrawerAdapter adapter = new NavigationDrawerAdapter(navigationItems);
adapter.setNavigationDrawerCallbacks(this);
mDrawerList.setAdapter(adapter);
selectItem(mCurrentSelectedPosition);
return view;
}
public boolean isDrawerOpen() {
return mDrawerLayout != null && mDrawerLayout.isDrawerOpen(mFragmentContainerView);
}
public ActionBarDrawerToggle getActionBarDrawerToggle() {
return mActionBarDrawerToggle;
}
public DrawerLayout getDrawerLayout() {
return mDrawerLayout;
}
@Override
public void onNavigationDrawerItemSelected(int position) {
selectItem(position);
}
public List<NavigationItem> getMenu() {
List<NavigationItem> items = new ArrayList<NavigationItem>();
items.add(new NavigationItem("Console", getResources().getDrawable(R.drawable.ic_menu_check)));
items.add(new NavigationItem("Peers", getResources().getDrawable(R.drawable.ic_menu_check)));
items.add(new NavigationItem("Block Chain", getResources().getDrawable(R.drawable.ic_menu_check)));
items.add(new NavigationItem("New Transaction", getResources().getDrawable(R.drawable.ic_menu_check)));
items.add(new NavigationItem("Pending Transactions", getResources().getDrawable(R.drawable.ic_menu_check)));
items.add(new NavigationItem("Debug Info", getResources().getDrawable(R.drawable.ic_menu_check)));
return items;
}
/**
* Users of this fragment must call this method to set up the navigation drawer interactions.
*
* @param fragmentId The android:id of this fragment in its activity's layout.
* @param drawerLayout The DrawerLayout containing this fragment's UI.
* @param toolbar The Toolbar of the activity.
*/
public void setup(int fragmentId, DrawerLayout drawerLayout, Toolbar toolbar) {
mFragmentContainerView = getActivity().findViewById(fragmentId);
mDrawerLayout = drawerLayout;
mDrawerLayout.setStatusBarBackgroundColor(getResources().getColor(R.color.myPrimaryDarkColor));
mActionBarDrawerToggle = new ActionBarDrawerToggle(getActivity(), mDrawerLayout, toolbar, R.string.drawer_open, R.string.drawer_close) {
@Override
public void onDrawerClosed(View drawerView) {
super.onDrawerClosed(drawerView);
if (!isAdded()) return;
getActivity().invalidateOptionsMenu(); // calls onPrepareOptionsMenu()
}
@Override
public void onDrawerOpened(View drawerView) {
super.onDrawerOpened(drawerView);
if (!isAdded()) return;
if (!mUserLearnedDrawer) {
mUserLearnedDrawer = true;
SharedPreferences sp = PreferenceManager
.getDefaultSharedPreferences(getActivity());
sp.edit().putBoolean(PREF_USER_LEARNED_DRAWER, true).apply();
}
getActivity().invalidateOptionsMenu(); // calls onPrepareOptionsMenu()
}
};
// If the user hasn't 'learned' about the drawer, open it to introduce them to the drawer,
// per the navigation drawer design guidelines.
if (!mUserLearnedDrawer && !mFromSavedInstanceState) {
mDrawerLayout.openDrawer(mFragmentContainerView);
}
// Defer code dependent on restoration of previous instance state.
mDrawerLayout.post(new Runnable() {
@Override
public void run() {
mActionBarDrawerToggle.syncState();
}
});
mDrawerLayout.setDrawerListener(mActionBarDrawerToggle);
}
private void selectItem(int position) {
mCurrentSelectedPosition = position;
if (mDrawerLayout != null) {
mDrawerLayout.closeDrawer(mFragmentContainerView);
}
if (mCallbacks != null) {
mCallbacks.onNavigationDrawerItemSelected(position);
}
((NavigationDrawerAdapter) mDrawerList.getAdapter()).selectPosition(position);
}
public void openDrawer() {
mDrawerLayout.openDrawer(mFragmentContainerView);
}
public void closeDrawer() {
mDrawerLayout.closeDrawer(mFragmentContainerView);
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mCallbacks = (NavigationDrawerCallbacks) activity;
} catch (ClassCastException e) {
throw new ClassCastException("Activity must implement NavigationDrawerCallbacks.");
}
}
@Override
public void onDetach() {
super.onDetach();
mCallbacks = null;
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(STATE_SELECTED_POSITION, mCurrentSelectedPosition);
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
// Forward the new configuration the drawer toggle component.
mActionBarDrawerToggle.onConfigurationChanged(newConfig);
}
}

View File

@ -0,0 +1,33 @@
package org.ethereum.ethereum_android;
import android.graphics.drawable.Drawable;
/**
* Created by poliveira on 24/10/2014.
*/
public class NavigationItem {
private String mText;
private Drawable mDrawable;
public NavigationItem(String text, Drawable drawable) {
mText = text;
mDrawable = drawable;
}
public String getText() {
return mText;
}
public void setText(String text) {
mText = text;
}
public Drawable getDrawable() {
return mDrawable;
}
public void setDrawable(Drawable drawable) {
mDrawable = drawable;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 232 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 329 B

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_selected="true" android:drawable="@drawable/selected_gray"/>
<item android:state_pressed="true" android:drawable="@drawable/pressed_gray"/>
</selector>

View File

@ -1,25 +1,30 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">
android:layout_height="match_parent" tools:context=".MainActivity">
<include android:id="@+id/toolbar_actionbar" layout="@layout/toolbar_default"
android:layout_width="match_parent" android:layout_height="wrap_content" />
<android.support.v4.widget.DrawerLayout android:id="@+id/drawer"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_height="match_parent"
android:layout_below="@+id/toolbar_actionbar">
<FrameLayout android:id="@+id/container" android:layout_width="match_parent"
android:clickable="true" android:layout_height="match_parent" />
<!-- android:layout_marginTop="?android:attr/actionBarSize"-->
<fragment android:id="@+id/fragment_drawer"
android:name="org.ethereum.ethereum_android.NavigationDrawerFragment"
android:layout_width="@dimen/navigation_drawer_width"
android:layout_height="match_parent" android:layout_gravity="start"
app:layout="@layout/fragment_navigation_drawer" />
</android.support.v4.widget.DrawerLayout>
<TextView android:text="@string/hello_world" android:layout_width="wrap_content"
android:scrollbars = "vertical" android:gravity="bottom"
android:fontFamily="Arial" android:typeface="serif" android:textSize="8sp"
android:id="@+id/text1" android:layout_height="wrap_content" />
<LinearLayout android:id="@+id/footer" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:orientation="horizontal"
android:layout_alignParentBottom="true" style="@android:style/ButtonBar">
<Button android:id="@+id/consoleButton" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_weight="1"
android:text="@string/menu_console" />
<Button android:id="@+id/walletButton" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_weight="1"
android:text="@string/menu_wallet" />
</LinearLayout>
</RelativeLayout>

View File

@ -0,0 +1,9 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="48dp" android:layout_width="match_parent">
<TextView android:id="@+id/item_name" android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:attr/selectableItemBackground" android:drawablePadding="16dp"
android:textSize="14sp" android:gravity="center_vertical"
android:textColor="@color/myTextPrimaryColor" />
</LinearLayout>

View File

@ -0,0 +1,5 @@
<android.support.v7.widget.RecyclerView android:id="@+id/drawerList"
xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
android:clickable="true" android:scrollbars="vertical"
android:layout_height="match_parent" android:background="@color/myDrawerBackground" />

View File

@ -0,0 +1,4 @@
<android.support.v7.widget.Toolbar xmlns:android="http://schemas.android.com/apk/res/android"
style="@style/ToolBarStyle" android:layout_width="match_parent"
android:layout_height="wrap_content" android:background="?attr/colorPrimary"
android:minHeight="@dimen/abc_action_bar_default_height_material" />

View File

@ -0,0 +1,28 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/myPrimaryColor</item>
<item name="colorPrimaryDark">@color/myPrimaryDarkColor</item>
<item name="colorAccent">@color/myAccentColor</item>
<item name="android:textColorPrimary">@color/myTextPrimaryColor</item>
<item name="android:navigationBarColor">@color/myNavigationColor</item>
<item name="drawerArrowStyle">@style/DrawerArrowStyle</item>
<item name="android:windowBackground">@color/myWindowBackground</item>
<item name="android:windowContentTransitions">true</item>
</style>
<style name="DrawerArrowStyle" parent="Widget.AppCompat.DrawerArrowToggle">
<item name="spinBars">true</item>
<item name="color">@android:color/white</item>
</style>
<style name="ToolBarStyle" parent="">
<item name="android:elevation">@dimen/toolbar_elevation</item>
<item name="popupTheme">@style/ThemeOverlay.AppCompat.Light</item>
<item name="theme">@style/ThemeOverlay.AppCompat.Dark.ActionBar</item>
</style>
</resources>

View File

@ -3,4 +3,5 @@
(such as screen margins) for screens with more than 820dp of available width. This
would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
<dimen name="activity_horizontal_margin">64dp</dimen>
<dimen name="toolbar_elevation">4dp</dimen>
</resources>

View File

@ -0,0 +1,21 @@
<resources>
<!--
Check Android Theme colors for color reference:
https://developer.android.com/training/material/images/ThemeColors.png
Refer also to Color palette for the UI colors you should use:
http://www.google.com/design/spec/style/color.html#color-ui-color-palette
-->
<color name="myPrimaryColor">#2196F3</color>
<color name="myPrimaryDarkColor">#1976D2</color>
<color name="myAccentColor">#FF4081</color>
<color name="myDrawerBackground">#FFF</color>
<color name="myWindowBackground">#FFF</color>
<color name="myTextPrimaryColor">#212121</color>
<color name="myNavigationColor">#000</color>
<drawable name="pressed_gray">#0c000000</drawable>
<drawable name="selected_gray">#2c000000</drawable>
</resources>

View File

@ -6,4 +6,5 @@
<!-- Per the design guidelines, navigation drawers should be between 240dp and 320dp:
https://developer.android.com/design/patterns/navigation-drawer.html -->
<dimen name="navigation_drawer_width">240dp</dimen>
<dimen name="toolbar_elevation">4dp</dimen>
</resources>

View File

@ -13,4 +13,6 @@
<string name="navigation_drawer_close">Close navigation drawer</string>
<string name="action_example">Example action</string>
<string name="drawer_open">Menu opened</string>
<string name="drawer_close">Menu closed</string>
</resources>

View File

@ -1,8 +1,29 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/myPrimaryColor</item>
<item name="colorPrimaryDark">@color/myPrimaryDarkColor</item>
<item name="colorAccent">@color/myAccentColor</item>
<item name="android:textColorPrimary">@color/myTextPrimaryColor</item>
<item name="drawerArrowStyle">@style/DrawerArrowStyle</item>
<item name="android:windowBackground">@color/myWindowBackground</item>
</style>
<style name="DrawerArrowStyle" parent="Widget.AppCompat.DrawerArrowToggle">
<item name="spinBars">true</item>
<item name="color">@android:color/white</item>
</style>
<style name="ToolBarStyle" parent="">
<item name="popupTheme">@style/ThemeOverlay.AppCompat.Light</item>
<item name="theme">@style/ThemeOverlay.AppCompat.Dark.ActionBar</item>
</style>
</resources>

View File

@ -97,7 +97,7 @@ dependencies {
//compile "com.octo.android.robospice:robospice-spring-android:1.4.14"
//compile 'com.octo.android.robospice:robospice-ormlite:1.4.14'
compile('io.netty:netty-all:4.0.18.Final') {
compile('io.netty:netty-all:4.0.21.Final') {
exclude group: 'commons-logging', module: 'commons-logging'
}
compile "com.madgag.spongycastle:core:${scastleVersion}"

View File

@ -0,0 +1,25 @@
package org.ethereum;
import org.ethereum.config.SystemProperties;
import org.ethereum.facade.Ethereum;
import org.robospring.RoboSpring;
import org.springframework.context.ApplicationContext;
/**
* Created by userica on 06.05.2015.
*/
public class EthereumFactory {
public static ApplicationContext context = null;
public static Ethereum getEthereum(android.content.Context androidContext) {
RoboSpring.autowire(androidContext);
context = RoboSpring.getContext();
Ethereum ethereum = context.getBean(org.ethereum.facade.Ethereum.class);
ethereum.setContext(context);
return ethereum;
}
}

View File

@ -1,26 +0,0 @@
package org.ethereum;
import org.ethereum.cli.CLIInterface;
import org.ethereum.config.SystemProperties;
import org.ethereum.facade.Ethereum;
import org.robospring.RoboSpring;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyClass {
public static ApplicationContext context = null;
public static void start(android.content.Context androidContext) {
//CLIInterface.call(args);RoboSpring.getContext("applicationContext.xml");
RoboSpring.autowire(androidContext);
context = RoboSpring.getContext();
Ethereum ethereum = context.getBean(Ethereum.class);
ethereum.setContext(context);
ethereum.connect(SystemProperties.CONFIG.activePeerIP(),
SystemProperties.CONFIG.activePeerPort());
}
}

View File

@ -0,0 +1,18 @@
package org.ethereum.config;
public class Constants {
public static int GENESIS_DIFFICULTY = 131072;
public static int MAXIMUM_EXTRA_DATA_SIZE = 1024;
public static int EPOCH_DURATION = 30_000;
public static int GENESIS_GAS_LIMIT = 3_141_592;
public static int MIN_GAS_LIMIT = 125000;
public static int GAS_LIMIT_BOUND_DIVISOR = 1024;
public static int MINIMUM_DIFFICULTY = 131072;
public static int DIFFICULTY_BOUND_DIVISOR = 2048;
public static int DURATION_LIMIT = 8;
public static int UNCLE_GENERATION_LIMIT = 7;
public static int UNCLE_LIST_LIMIT = 2;
}

View File

@ -0,0 +1,7 @@
package org.ethereum.core;
public enum ImportResult {
SUCCESS,
EXIST,
NO_PARENT
}

View File

@ -0,0 +1,78 @@
package org.ethereum.datasource;
import org.ethereum.db.ByteArrayWrapper;
import org.ethereum.util.ByteUtil;
import org.iq80.leveldb.DBException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import static org.ethereum.util.ByteUtil.wrap;
public class HashMapDB implements KeyValueDataSource {
Map<ByteArrayWrapper, byte[]> storage = new HashMap<>();
@Override
public void delete(byte[] arg0) throws DBException {
storage.remove(wrap(arg0));
}
@Override
public byte[] get(byte[] arg0) throws DBException {
return storage.get(wrap(arg0));
}
@Override
public byte[] put(byte[] key, byte[] value) throws DBException {
return storage.put(wrap(key), value);
}
/**
* Returns the number of items added to this Mock DB
*
* @return int
*/
public int getAddedItems() {
return storage.size();
}
@Override
public void init() {
}
@Override
public void setName(String name) {
}
@Override
public Set<byte[]> keys() {
Set<byte[]> keys = new HashSet<>();
for (ByteArrayWrapper key : storage.keySet()){
keys.add(key.getData());
}
return keys;
}
@Override
public void updateBatch(Map<byte[], byte[]> rows) {
for (byte[] key : rows.keySet()){
byte[] value = rows.get(key);
storage.put(wrap(key), value);
}
}
@Override
public void close() {
}
}

View File

@ -0,0 +1,36 @@
package org.ethereum.facade;
import org.ethereum.net.eth.EthHandler;
import org.ethereum.net.shh.ShhHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
//import org.springframework.context.annotation.AnnotationConfigApplicationContext;
//import org.springframework.stereotype.Component;
/**
* @author Roman Mandeleil
* @since 13.11.2014
*/
//@Component
public class EthereumFactory {
private static final Logger logger = LoggerFactory.getLogger("general");
public static ApplicationContext context = null;
public static Ethereum createEthereum() {
return createEthereum(DefaultConfig.class);
}
public static Ethereum createEthereum(Class clazz) {
logger.info("capability eth version: [{}]", EthHandler.VERSION);
logger.info("capability shh version: [{}]", ShhHandler.VERSION);
// context = new AnnotationConfigApplicationContext(clazz);
return context.getBean(Ethereum.class);
}
}

View File

@ -0,0 +1,66 @@
package org.ethereum.jsontestsuite;
import org.ethereum.jsontestsuite.model.AccountTck;
import org.ethereum.jsontestsuite.model.BlockHeaderTck;
import org.ethereum.jsontestsuite.model.BlockTck;
import java.util.List;
import java.util.Map;
public class BlockTestCase {
private List<BlockTck> blocks;
private BlockHeaderTck genesisBlockHeader;
private String genesisRLP;
private Map<String, AccountTck> pre;
private Map<String, AccountTck> postState;
public BlockTestCase() {
}
public List<BlockTck> getBlocks() {
return blocks;
}
public void setBlocks(List<BlockTck> blocks) {
this.blocks = blocks;
}
public BlockHeaderTck getGenesisBlockHeader() {
return genesisBlockHeader;
}
public void setGenesisBlockHeader(BlockHeaderTck genesisBlockHeader) {
this.genesisBlockHeader = genesisBlockHeader;
}
public Map<String, AccountTck> getPre() {
return pre;
}
public String getGenesisRLP() {
return genesisRLP;
}
public void setPre(Map<String, AccountTck> pre) {
this.pre = pre;
}
public Map<String, AccountTck> getPostState() {
return postState;
}
public void setPostState(Map<String, AccountTck> postState) {
this.postState = postState;
}
@Override
public String toString() {
return "BlockTestCase{" +
"blocks=" + blocks +
", genesisBlockHeader=" + genesisBlockHeader +
", pre=" + pre +
'}';
}
}

View File

@ -0,0 +1,37 @@
package org.ethereum.jsontestsuite;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.type.JavaType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class BlockTestSuite {
private Logger logger = LoggerFactory.getLogger("TCK-Test");
Map<String, BlockTestCase> testCases = new HashMap<>();
public BlockTestSuite(String json) throws IOException {
ObjectMapper mapper = new ObjectMapper();
JavaType type = mapper.getTypeFactory().
constructMapType(HashMap.class, String.class, BlockTestCase.class);
testCases = new ObjectMapper().readValue(json, type);
}
public Map<String, BlockTestCase> getTestCases() {
return testCases;
}
@Override
public String toString() {
return "BlockTestSuite{" +
"testCases=" + testCases +
'}';
}
}

View File

@ -0,0 +1,97 @@
package org.ethereum.jsontestsuite;
import org.ethereum.crypto.ECIESCoder;
import org.ethereum.crypto.ECKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongycastle.jcajce.provider.asymmetric.ec.IESCipher;
import org.spongycastle.util.encoders.Hex;
import java.math.BigInteger;
/**
* @author Roman Mandeleil
* @since 08.02.2015
*/
public class CryptoTestCase {
private static Logger logger = LoggerFactory.getLogger("TCK-Test");
private String decryption_type = "";
private String key = "";
private String cipher = "";
private String payload = "";
public CryptoTestCase(){
}
public void execute(){
byte[] key = Hex.decode(this.key);
byte[] cipher = Hex.decode(this.cipher);
ECKey ecKey = ECKey.fromPrivate(key);
byte[] resultPayload = new byte[0];
if (decryption_type.equals("aes_ctr"))
resultPayload = ecKey.decryptAES(cipher);
if (decryption_type.equals("ecies_sec1_altered"))
try {
resultPayload = ECIESCoder.decrypt(new BigInteger(Hex.toHexString(key), 16), cipher);
} catch (Throwable e) {e.printStackTrace();}
if (!Hex.toHexString(resultPayload).equals(payload)){
String error = String.format("payload should be: %s, but got that result: %s ",
payload, Hex.toHexString(resultPayload));
logger.info(error);
System.exit(-1);
}
}
public String getDecryption_type() {
return decryption_type;
}
public void setDecryption_type(String decryption_type) {
this.decryption_type = decryption_type;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getCipher() {
return cipher;
}
public void setCipher(String cipher) {
this.cipher = cipher;
}
public String getPayload() {
return payload;
}
public void setPayload(String payload) {
this.payload = payload;
}
@Override
public String toString() {
return "CryptoTestCase{" +
"decryption_type='" + decryption_type + '\'' +
", key='" + key + '\'' +
", cipher='" + cipher + '\'' +
", payload='" + payload + '\'' +
'}';
}
}

View File

@ -0,0 +1,68 @@
package org.ethereum.jsontestsuite.builder;
import org.ethereum.core.AccountState;
import org.ethereum.db.ContractDetails;
import org.ethereum.jsontestsuite.model.AccountTck;
import org.ethereum.vm.DataWord;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.Map;
import static org.ethereum.crypto.HashUtil.sha3;
import static org.ethereum.jsontestsuite.Utils.parseData;
public class AccountBuilder {
public static StateWrap build(AccountTck account) {
ContractDetails details = new ContractDetails();
details.setCode(parseData(account.getCode()));
details.setStorage(convertStorage(account.getStorage()));
AccountState state = new AccountState();
state.addToBalance(new BigInteger(account.getBalance()));
state.setNonce(new BigInteger(account.getNonce()));
state.setStateRoot(details.getStorageHash());
state.setCodeHash(sha3(details.getCode()));
return new StateWrap(state, details);
}
private static Map<DataWord, DataWord> convertStorage(Map<String, String> storageTck) {
Map<DataWord, DataWord> storage = new HashMap<>();
for (String keyTck : storageTck.keySet()) {
String valueTck = storageTck.get(keyTck);
DataWord key = new DataWord(parseData(keyTck));
DataWord value = new DataWord(parseData(valueTck));
storage.put(key, value);
}
return storage;
}
public static class StateWrap {
AccountState accountState;
ContractDetails contractDetails;
public StateWrap(AccountState accountState, ContractDetails contractDetails) {
this.accountState = accountState;
this.contractDetails = contractDetails;
}
public AccountState getAccountState() {
return accountState;
}
public ContractDetails getContractDetails() {
return contractDetails;
}
}
}

View File

@ -0,0 +1,73 @@
package org.ethereum.jsontestsuite.builder;
import org.ethereum.core.Block;
import org.ethereum.core.BlockHeader;
import org.ethereum.core.Transaction;
import org.ethereum.jsontestsuite.Env;
import org.ethereum.jsontestsuite.model.BlockHeaderTck;
import org.ethereum.jsontestsuite.model.TransactionTck;
import org.ethereum.util.BIUtil;
import org.ethereum.util.ByteUtil;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import static org.ethereum.util.BIUtil.exitLong;
import static org.ethereum.util.BIUtil.isCovers;
import static org.ethereum.util.BIUtil.toBI;
import static org.ethereum.util.ByteUtil.byteArrayToLong;
public class BlockBuilder {
public static Block build(BlockHeaderTck header,
List<TransactionTck> transactionsTck,
List<BlockHeaderTck> unclesTck) {
if (header == null) return null;
List<BlockHeader> uncles = new ArrayList<>();
if (unclesTck != null) for (BlockHeaderTck uncle : unclesTck)
uncles.add(BlockHeaderBuilder.build(uncle));
List<Transaction> transactions = new ArrayList<>();
if (transactionsTck != null) for (TransactionTck tx : transactionsTck)
transactions.add(TransactionBuilder.build(tx));
BlockHeader blockHeader = BlockHeaderBuilder.build(header);
Block block = new Block(
blockHeader,
transactions, uncles);
return block;
}
public static Block build(Env env){
// TODO: it can be removed in the future when block will be adapted to 32 bytes range gas limit
long gasLimit = byteArrayToLong(env.getCurrentGasLimit());
if (exitLong(toBI(env.getCurrentGasLimit())))
gasLimit = Long.MAX_VALUE;
Block block = new Block(
ByteUtil.EMPTY_BYTE_ARRAY,
ByteUtil.EMPTY_BYTE_ARRAY,
env.getCurrentCoinbase(),
ByteUtil.EMPTY_BYTE_ARRAY,
env.getCurrentDifficulty(),
byteArrayToLong(env.getCurrentNumber()),
gasLimit,
0L,
byteArrayToLong(env.getCurrentTimestamp()),
new byte[32],
ByteUtil.ZERO_BYTE_ARRAY,
ByteUtil.ZERO_BYTE_ARRAY,
null, null);
return block;
}
}

View File

@ -0,0 +1,40 @@
package org.ethereum.jsontestsuite.builder;
import org.ethereum.core.Block;
import org.ethereum.core.BlockHeader;
import org.ethereum.jsontestsuite.Utils;
import org.ethereum.jsontestsuite.model.BlockHeaderTck;
import java.math.BigInteger;
import static org.ethereum.jsontestsuite.Utils.parseData;
import static org.ethereum.jsontestsuite.Utils.parseNumericData;
public class BlockHeaderBuilder {
public static BlockHeader build(BlockHeaderTck headerTck){
BlockHeader header = new BlockHeader(
parseData(headerTck.getParentHash()),
parseData(headerTck.getUncleHash()),
parseData(headerTck.getCoinbase()),
parseData(headerTck.getBloom()),
parseNumericData(headerTck.getDifficulty()),
new BigInteger(headerTck.getNumber()).longValue(),
new BigInteger(headerTck.getGasLimit()).longValue(),
new BigInteger(headerTck.getGasUsed()).longValue(),
new BigInteger(headerTck.getTimestamp()).longValue(),
parseData(headerTck.getExtraData()),
parseData(headerTck.getMixHash()),
parseData(headerTck.getNonce())
);
header.setReceiptsRoot(parseData(headerTck.getReceiptTrie()));
header.setTransactionsRoot(parseData(headerTck.getTransactionsTrie()));
header.setStateRoot(parseData(headerTck.getStateRoot()));
return header;
}
}

View File

@ -0,0 +1,23 @@
package org.ethereum.jsontestsuite.builder;
import org.ethereum.jsontestsuite.Env;
import org.ethereum.jsontestsuite.model.EnvTck;
import static org.ethereum.jsontestsuite.Utils.parseData;
import static org.ethereum.jsontestsuite.Utils.parseNumericData;
import static org.ethereum.jsontestsuite.Utils.parseVarData;
public class EnvBuilder {
public static Env build(EnvTck envTck){
byte[] coinbase = parseData(envTck.getCurrentCoinbase());
byte[] difficulty = parseVarData(envTck.getCurrentDifficulty());
byte[] gasLimit = parseVarData(envTck.getCurrentGasLimit());
byte[] number = parseNumericData(envTck.getCurrentNumber());
byte[] timestamp = parseNumericData(envTck.getCurrentTimestamp());
byte[] hash = parseData(envTck.getPreviousHash());
return new Env(coinbase, difficulty, gasLimit, number, timestamp, hash);
}
}

View File

@ -0,0 +1,36 @@
package org.ethereum.jsontestsuite.builder;
import org.ethereum.jsontestsuite.Utils;
import org.ethereum.jsontestsuite.model.LogTck;
import org.ethereum.vm.DataWord;
import org.ethereum.vm.LogInfo;
import java.util.ArrayList;
import java.util.List;
import static org.ethereum.jsontestsuite.Utils.parseData;
public class LogBuilder {
public static LogInfo build(LogTck logTck){
byte[] address = parseData(logTck.getAddress());
byte[] data = parseData(logTck.getData());
List<DataWord> topics = new ArrayList<>();
for (String topicTck : logTck.getTopics())
topics.add(new DataWord(parseData(topicTck)));
return new LogInfo(address, topics, data);
}
public static List<LogInfo> build(List<LogTck> logs){
List<LogInfo> outLogs = new ArrayList<>();
for (LogTck log : logs)
outLogs.add(build(log));
return outLogs;
}
}

View File

@ -0,0 +1,42 @@
package org.ethereum.jsontestsuite.builder;
import org.ethereum.core.AccountState;
import org.ethereum.datasource.HashMapDB;
import org.ethereum.datasource.KeyValueDataSource;
import org.ethereum.db.ByteArrayWrapper;
import org.ethereum.db.ContractDetails;
import org.ethereum.db.RepositoryDummy;
import org.ethereum.db.RepositoryImpl;
import org.ethereum.facade.Repository;
import org.ethereum.jsontestsuite.model.AccountTck;
import java.util.HashMap;
import java.util.Map;
import static org.ethereum.jsontestsuite.Utils.parseData;
import static org.ethereum.util.ByteUtil.wrap;
public class RepositoryBuilder {
public static Repository build(Map<String, AccountTck> accounts){
HashMap<ByteArrayWrapper, AccountState> stateBatch = new HashMap<>();
HashMap<ByteArrayWrapper, ContractDetails> detailsBatch = new HashMap<>();
for (String address : accounts.keySet()) {
AccountTck accountTCK = accounts.get(address);
AccountBuilder.StateWrap stateWrap = AccountBuilder.build(accountTCK);
AccountState state = stateWrap.getAccountState();
ContractDetails details = stateWrap.getContractDetails();
stateBatch.put(wrap(parseData(address)), state);
detailsBatch.put(wrap(parseData(address)), details);
}
RepositoryImpl repositoryDummy = new RepositoryImpl(new HashMapDB(), new HashMapDB());
repositoryDummy.updateBatch(stateBatch, detailsBatch);
return repositoryDummy;
}
}

View File

@ -0,0 +1,52 @@
package org.ethereum.jsontestsuite.builder;
import org.ethereum.core.Transaction;
import org.ethereum.jsontestsuite.model.TransactionTck;
import org.spongycastle.util.BigIntegers;
import java.math.BigInteger;
import static org.ethereum.jsontestsuite.Utils.*;
import static org.ethereum.util.BIUtil.exitLong;
import static org.ethereum.util.BIUtil.toBI;
import static org.ethereum.util.ByteUtil.byteArrayToLong;
public class TransactionBuilder {
public static Transaction build(TransactionTck transactionTck) {
// TODO: it can be removed in the future when block will be adapted to 32 bytes range gas limit
BigInteger gasLimit = toBI(parseVarData(transactionTck.getGasLimit()));
if (exitLong(gasLimit))
gasLimit = new BigInteger(Long.MAX_VALUE + "");
Transaction transaction;
if (transactionTck.getSecretKey() != null){
transaction = new Transaction(
parseVarData(transactionTck.getNonce()),
parseVarData(transactionTck.getGasPrice()),
BigIntegers.asUnsignedByteArray(gasLimit),
parseData(transactionTck.getTo()),
parseVarData(transactionTck.getValue()),
parseData(transactionTck.getData()));
transaction.sign(parseData(transactionTck.getSecretKey()));
} else {
transaction = new Transaction(
parseNumericData(transactionTck.getNonce()),
parseNumericData(transactionTck.getGasPrice()),
BigIntegers.asUnsignedByteArray(gasLimit),
parseData(transactionTck.getTo()),
parseNumericData(transactionTck.getValue()),
parseData(transactionTck.getData()),
parseData(transactionTck.getR()),
parseData(transactionTck.getS()),
parseByte(transactionTck.getV())
);
}
return transaction;
}
}

View File

@ -0,0 +1,68 @@
package org.ethereum.jsontestsuite.model;
import org.ethereum.db.ContractDetails;
import org.ethereum.util.ByteUtil;
import org.ethereum.vm.DataWord;
import org.json.simple.JSONObject;
import org.spongycastle.util.encoders.Hex;
import java.math.BigInteger;
import java.util.*;
/**
* @author Roman Mandeleil
* @since 28.06.2014
*/
public class AccountTck {
String balance;
String code;
String nonce;
Map<String, String> storage = new HashMap<>();
public AccountTck() {
}
public String getBalance() {
return balance;
}
public void setBalance(String balance) {
this.balance = balance;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getNonce() {
return nonce;
}
public void setNonce(String nonce) {
this.nonce = nonce;
}
public Map<String, String> getStorage() {
return storage;
}
public void setStorage(Map<String, String> storage) {
this.storage = storage;
}
@Override
public String toString() {
return "AccountState2{" +
"balance='" + balance + '\'' +
", code='" + code + '\'' +
", nonce='" + nonce + '\'' +
", storage=" + storage +
'}';
}
}

View File

@ -0,0 +1,184 @@
package org.ethereum.jsontestsuite.model;
public class BlockHeaderTck {
String bloom;
String coinbase;
String difficulty;
String extraData;
String gasLimit;
String gasUsed;
String hash;
String mixHash;
String nonce;
String number;
String parentHash;
String receiptTrie;
String seedHash;
String stateRoot;
String timestamp;
String transactionsTrie;
String uncleHash;
public BlockHeaderTck() {
}
public String getBloom() {
return bloom;
}
public void setBloom(String bloom) {
this.bloom = bloom;
}
public String getCoinbase() {
return coinbase;
}
public void setCoinbase(String coinbase) {
this.coinbase = coinbase;
}
public String getDifficulty() {
return difficulty;
}
public void setDifficulty(String difficulty) {
this.difficulty = difficulty;
}
public String getExtraData() {
return extraData;
}
public void setExtraData(String extraData) {
this.extraData = extraData;
}
public String getGasLimit() {
return gasLimit;
}
public void setGasLimit(String gasLimit) {
this.gasLimit = gasLimit;
}
public String getGasUsed() {
return gasUsed;
}
public void setGasUsed(String gasUsed) {
this.gasUsed = gasUsed;
}
public String getHash() {
return hash;
}
public void setHash(String hash) {
this.hash = hash;
}
public String getMixHash() {
return mixHash;
}
public void setMixHash(String mixHash) {
this.mixHash = mixHash;
}
public String getNonce() {
return nonce;
}
public void setNonce(String nonce) {
this.nonce = nonce;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
public String getParentHash() {
return parentHash;
}
public void setParentHash(String parentHash) {
this.parentHash = parentHash;
}
public String getReceiptTrie() {
return receiptTrie;
}
public void setReceiptTrie(String receiptTrie) {
this.receiptTrie = receiptTrie;
}
public String getSeedHash() {
return seedHash;
}
public void setSeedHash(String seedHash) {
this.seedHash = seedHash;
}
public String getStateRoot() {
return stateRoot;
}
public void setStateRoot(String stateRoot) {
this.stateRoot = stateRoot;
}
public String getTimestamp() {
return timestamp;
}
public void setTimestamp(String timestamp) {
this.timestamp = timestamp;
}
public String getTransactionsTrie() {
return transactionsTrie;
}
public void setTransactionsTrie(String transactionsTrie) {
this.transactionsTrie = transactionsTrie;
}
public String getUncleHash() {
return uncleHash;
}
public void setUncleHash(String uncleHash) {
this.uncleHash = uncleHash;
}
@Override
public String toString() {
return "BlockHeader{" +
"bloom='" + bloom + '\'' +
", coinbase='" + coinbase + '\'' +
", difficulty='" + difficulty + '\'' +
", extraData='" + extraData + '\'' +
", gasLimit='" + gasLimit + '\'' +
", gasUsed='" + gasUsed + '\'' +
", hash='" + hash + '\'' +
", mixHash='" + mixHash + '\'' +
", nonce='" + nonce + '\'' +
", number='" + number + '\'' +
", parentHash='" + parentHash + '\'' +
", receiptTrie='" + receiptTrie + '\'' +
", seedHash='" + seedHash + '\'' +
", stateRoot='" + stateRoot + '\'' +
", timestamp='" + timestamp + '\'' +
", transactionsTrie='" + transactionsTrie + '\'' +
", uncleHash='" + uncleHash + '\'' +
'}';
}
}

View File

@ -0,0 +1,57 @@
package org.ethereum.jsontestsuite.model;
import java.util.List;
public class BlockTck {
BlockHeaderTck blockHeader;
List<TransactionTck> transactions;
List<BlockHeaderTck> uncleHeaders;
String rlp;
public BlockTck() {
}
public BlockHeaderTck getBlockHeader() {
return blockHeader;
}
public void setBlockHeader(BlockHeaderTck blockHeader) {
this.blockHeader = blockHeader;
}
public String getRlp() {
return rlp;
}
public void setRlp(String rlp) {
this.rlp = rlp;
}
public List<TransactionTck> getTransactions() {
return transactions;
}
public void setTransactions(List<TransactionTck> transactions) {
this.transactions = transactions;
}
public List<BlockHeaderTck> getUncleHeaders() {
return uncleHeaders;
}
public void setUncleHeaders(List<BlockHeaderTck> uncleHeaders) {
this.uncleHeaders = uncleHeaders;
}
@Override
public String toString() {
return "Block{" +
"blockHeader=" + blockHeader +
", transactions=" + transactions +
", uncleHeaders=" + uncleHeaders +
", rlp='" + rlp + '\'' +
'}';
}
}

View File

@ -0,0 +1,62 @@
package org.ethereum.jsontestsuite.model;
public class EnvTck {
String currentCoinbase;
String currentDifficulty;
String currentGasLimit;
String currentNumber;
String currentTimestamp;
String previousHash;
public EnvTck() {
}
public String getCurrentCoinbase() {
return currentCoinbase;
}
public void setCurrentCoinbase(String currentCoinbase) {
this.currentCoinbase = currentCoinbase;
}
public String getCurrentDifficulty() {
return currentDifficulty;
}
public void setCurrentDifficulty(String currentDifficulty) {
this.currentDifficulty = currentDifficulty;
}
public String getCurrentGasLimit() {
return currentGasLimit;
}
public void setCurrentGasLimit(String currentGasLimit) {
this.currentGasLimit = currentGasLimit;
}
public String getCurrentNumber() {
return currentNumber;
}
public void setCurrentNumber(String currentNumber) {
this.currentNumber = currentNumber;
}
public String getCurrentTimestamp() {
return currentTimestamp;
}
public void setCurrentTimestamp(String currentTimestamp) {
this.currentTimestamp = currentTimestamp;
}
public String getPreviousHash() {
return previousHash;
}
public void setPreviousHash(String previousHash) {
this.previousHash = previousHash;
}
}

View File

@ -0,0 +1,46 @@
package org.ethereum.jsontestsuite.model;
import java.util.List;
public class LogTck {
String address;
String bloom;
String data;
List<String> topics;
public LogTck() {
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getBloom() {
return bloom;
}
public void setBloom(String bloom) {
this.bloom = bloom;
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
public List<String> getTopics() {
return topics;
}
public void setTopics(List<String> topics) {
this.topics = topics;
}
}

View File

@ -0,0 +1,125 @@
package org.ethereum.jsontestsuite.model;
import org.json.simple.JSONObject;
import java.math.BigInteger;
import static org.ethereum.util.ByteUtil.toHexString;
/**
* @author Roman Mandeleil
* @since 28.06.2014
*/
public class TransactionTck {
String data;
String gasLimit;
String gasPrice;
String nonce;
String r;
String s;
String to;
String v;
String value;
String secretKey;
public TransactionTck() {
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
public String getGasLimit() {
return gasLimit;
}
public void setGasLimit(String gasLimit) {
this.gasLimit = gasLimit;
}
public String getGasPrice() {
return gasPrice;
}
public void setGasPrice(String gasPrice) {
this.gasPrice = gasPrice;
}
public String getNonce() {
return nonce;
}
public void setNonce(String nonce) {
this.nonce = nonce;
}
public String getR() {
return r;
}
public void setR(String r) {
this.r = r;
}
public String getS() {
return s;
}
public void setS(String s) {
this.s = s;
}
public String getTo() {
return to;
}
public void setTo(String to) {
this.to = to;
}
public String getV() {
return v;
}
public void setV(String v) {
this.v = v;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public String getSecretKey() {
return secretKey;
}
public void setSecretKey(String secretKey) {
this.secretKey = secretKey;
}
@Override
public String toString() {
return "TransactionTck{" +
"data='" + data + '\'' +
", gasLimit='" + gasLimit + '\'' +
", gasPrice='" + gasPrice + '\'' +
", nonce='" + nonce + '\'' +
", r='" + r + '\'' +
", s='" + s + '\'' +
", to='" + to + '\'' +
", v='" + v + '\'' +
", value='" + value + '\'' +
", secretKey='" + secretKey + '\'' +
'}';
}
}

View File

@ -0,0 +1,95 @@
package org.ethereum.jsontestsuite.runners;
import org.ethereum.core.Block;
import org.ethereum.core.BlockchainImpl;
import org.ethereum.core.Transaction;
import org.ethereum.core.TransactionExecutor;
import org.ethereum.db.BlockStoreDummy;
import org.ethereum.facade.Repository;
import org.ethereum.jsontestsuite.Env;
import org.ethereum.jsontestsuite.StateTestCase;
import org.ethereum.jsontestsuite.TestProgramInvokeFactory;
import org.ethereum.jsontestsuite.builder.*;
import org.ethereum.jsontestsuite.validators.LogsValidator;
import org.ethereum.jsontestsuite.validators.OutputValidator;
import org.ethereum.jsontestsuite.validators.RepositoryValidator;
import org.ethereum.listener.EthereumListenerAdapter;
import org.ethereum.vm.LogInfo;
import org.ethereum.vm.ProgramInvokeFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongycastle.util.encoders.Hex;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
public class StateTestRunner {
private static Logger logger = LoggerFactory.getLogger("TCK-Test");
public static List<String> run(StateTestCase stateTestCase2) {
logger.info("");
Repository repository = RepositoryBuilder.build(stateTestCase2.getPre());
logger.info("loaded repository");
Transaction transaction = TransactionBuilder.build(stateTestCase2.getTransaction());
logger.info("transaction: {}", transaction.toString());
BlockchainImpl blockchain = new BlockchainImpl(new HashSet<Transaction>());
blockchain.setRepository(repository);
Env env = EnvBuilder.build(stateTestCase2.getEnv());
ProgramInvokeFactory invokeFactory = new TestProgramInvokeFactory(env);
Block block = BlockBuilder.build(env);
blockchain.setBestBlock(block);
blockchain.setProgramInvokeFactory(invokeFactory);
blockchain.startTracking();
Repository track = repository.startTracking();
TransactionExecutor executor =
new TransactionExecutor(transaction, env.getCurrentCoinbase(), track, new BlockStoreDummy(),
invokeFactory, blockchain.getBestBlock());
try{
executor.init();
executor.execute2();
executor.go();
executor.finalization();
} catch (StackOverflowError soe){
logger.error(" !!! StackOverflowError: update your java run command with -Xss32M !!!");
System.exit(-1);
}
track.commit();
repository.flush();
List<LogInfo> origLogs = executor.getResult().getLogInfoList();
List<LogInfo> postLogs = LogBuilder.build(stateTestCase2.getLogs());
List<String> logsResult = LogsValidator.valid(origLogs, postLogs);
Repository postRepository = RepositoryBuilder.build(stateTestCase2.getPost());
List<String> repoResults = RepositoryValidator.valid(repository, postRepository);
logger.info("--------- POST Validation---------");
List<String> outputResults =
OutputValidator.valid(Hex.toHexString(executor.getResult().getHReturn()), stateTestCase2.getOut());
List<String> results = new ArrayList<>();
results.addAll(repoResults);
results.addAll(logsResult);
results.addAll(outputResults);
for (String result : results) {
logger.error(result);
}
logger.info("\n\n");
return results;
}
}

View File

@ -0,0 +1,98 @@
package org.ethereum.jsontestsuite.validators;
import org.ethereum.core.AccountState;
import org.ethereum.db.ContractDetails;
import org.ethereum.vm.DataWord;
import org.spongycastle.util.encoders.Hex;
import java.math.BigInteger;
import java.util.*;
public class AccountValidator {
public static List<String> valid(String address, AccountState expectedState, ContractDetails expectedDetails,
AccountState currentState, ContractDetails currentDetails){
List<String> results = new ArrayList<>();
if (currentState == null || currentDetails == null){
String formattedString = String.format("Account: %s: expected but doesn't exist",
address);
results.add(formattedString);
return results;
}
if (expectedState == null || expectedDetails == null){
String formattedString = String.format("Account: %s: unexpected account in the repository",
address);
results.add(formattedString);
return results;
}
BigInteger expectedBalance = expectedState.getBalance();
if (currentState.getBalance().compareTo(expectedBalance) != 0) {
String formattedString = String.format("Account: %s: has unexpected balance, expected balance: %s found balance: %s",
address, expectedBalance.toString(), currentState.getBalance().toString());
results.add(formattedString);
}
BigInteger expectedNonce = expectedState.getNonce();
if (currentState.getNonce().compareTo(expectedNonce) != 0) {
String formattedString = String.format("Account: %s: has unexpected nonce, expected nonce: %s found nonce: %s",
address, expectedNonce.toString(), currentState.getNonce().toString());
results.add(formattedString);
}
if (!Arrays.equals(expectedDetails.getCode(), currentDetails.getCode())) {
String formattedString = String.format("Account: %s: has unexpected nonce, expected nonce: %s found nonce: %s",
address, Hex.toHexString(expectedDetails.getCode()), Hex.toHexString(currentDetails.getCode()));
results.add(formattedString);
}
// compare storage
Set<DataWord> currentKeys = currentDetails.getStorage().keySet();
Set<DataWord> expectedKeys = expectedDetails.getStorage().keySet();
Set<DataWord> checked = new HashSet<>();
for (DataWord key : currentKeys) {
DataWord currentValue = currentDetails.getStorage().get(key);
DataWord expectedValue = expectedDetails.getStorage().get(key);
if (expectedValue == null) {
String formattedString = String.format("Account: %s: has unexpected storage data: %s = %s",
address,
key,
currentValue);
results.add(formattedString);
continue;
}
if (!expectedValue.equals(currentValue)) {
String formattedString = String.format("Account: %s: has unexpected value, for key: %s , expectedValue: %s real value: %s",
address,
key.toString(),
expectedValue.toString(), currentValue.toString());
results.add(formattedString);
continue;
}
checked.add(key);
}
for (DataWord key : expectedKeys) {
if (!checked.contains(key)) {
String formattedString = String.format("Account: %s: doesn't exist expected storage key: %s",
address, key.toString());
results.add(formattedString);
}
}
return results;
}
}

View File

@ -0,0 +1,196 @@
package org.ethereum.jsontestsuite.validators;
import org.ethereum.core.BlockHeader;
import org.spongycastle.util.encoders.Hex;
import java.util.ArrayList;
import static org.ethereum.util.ByteUtil.toHexString;
public class BlockHeaderValidator {
public static ArrayList<String> valid(BlockHeader orig, BlockHeader valid) {
ArrayList<String> outputSummary = new ArrayList<>();
if (!toHexString(orig.getParentHash())
.equals(toHexString(valid.getParentHash()))) {
String output =
String.format("wrong block.parentHash: \n expected: %s \n got: %s",
toHexString(valid.getParentHash()),
toHexString(orig.getParentHash())
);
outputSummary.add(output);
}
if (!toHexString(orig.getUnclesHash())
.equals(toHexString(valid.getUnclesHash()))) {
String output =
String.format("wrong block.unclesHash: \n expected: %s \n got: %s",
toHexString(valid.getUnclesHash()),
toHexString(orig.getUnclesHash())
);
outputSummary.add(output);
}
if (!toHexString(orig.getCoinbase())
.equals(toHexString(valid.getCoinbase()))) {
String output =
String.format("wrong block.coinbase: \n expected: %s \n got: %s",
toHexString(valid.getCoinbase()),
toHexString(orig.getCoinbase())
);
outputSummary.add(output);
}
if (!toHexString(orig.getStateRoot())
.equals(toHexString(valid.getStateRoot()))) {
String output =
String.format("wrong block.stateRoot: \n expected: %s \n got: %s",
toHexString(valid.getStateRoot()),
toHexString(orig.getStateRoot())
);
outputSummary.add(output);
}
if (!toHexString(orig.getTxTrieRoot())
.equals(toHexString(valid.getTxTrieRoot()))) {
String output =
String.format("wrong block.txTrieRoot: \n expected: %s \n got: %s",
toHexString(valid.getTxTrieRoot()),
toHexString(orig.getTxTrieRoot())
);
outputSummary.add(output);
}
if (!toHexString(orig.getReceiptsRoot())
.equals(toHexString(valid.getReceiptsRoot()))) {
String output =
String.format("wrong block.receiptsRoot: \n expected: %s \n got: %s",
toHexString(valid.getReceiptsRoot()),
toHexString(orig.getReceiptsRoot())
);
outputSummary.add(output);
}
if (!toHexString(orig.getLogsBloom())
.equals(toHexString(valid.getLogsBloom()))) {
String output =
String.format("wrong block.logsBloom: \n expected: %s \n got: %s",
toHexString(valid.getLogsBloom()),
toHexString(orig.getLogsBloom())
);
outputSummary.add(output);
}
if (!toHexString(orig.getDifficulty())
.equals(toHexString(valid.getDifficulty()))) {
String output =
String.format("wrong block.difficulty: \n expected: %s \n got: %s",
toHexString(valid.getDifficulty()),
toHexString(orig.getDifficulty())
);
outputSummary.add(output);
}
if (orig.getTimestamp() != valid.getTimestamp()) {
String output =
String.format("wrong block.timestamp: \n expected: %d \n got: %d",
valid.getTimestamp(),
orig.getTimestamp()
);
outputSummary.add(output);
}
if (orig.getNumber() != valid.getNumber()) {
String output =
String.format("wrong block.number: \n expected: %d \n got: %d",
valid.getNumber(),
orig.getNumber()
);
outputSummary.add(output);
}
if (orig.getGasLimit() != valid.getGasLimit()) {
String output =
String.format("wrong block.gasLimit: \n expected: %d \n got: %d",
valid.getGasLimit(),
orig.getGasLimit()
);
outputSummary.add(output);
}
if (orig.getGasUsed() != valid.getGasUsed()) {
String output =
String.format("wrong block.gasUsed: \n expected: %d \n got: %d",
valid.getGasUsed(),
orig.getGasUsed()
);
outputSummary.add(output);
}
if (!toHexString(orig.getMixHash())
.equals(toHexString(valid.getMixHash()))) {
String output =
String.format("wrong block.mixHash: \n expected: %s \n got: %s",
toHexString(valid.getMixHash()),
toHexString(orig.getMixHash())
);
outputSummary.add(output);
}
if (!toHexString(orig.getExtraData())
.equals(toHexString(valid.getExtraData()))) {
String output =
String.format("wrong block.extraData: \n expected: %s \n got: %s",
toHexString(valid.getExtraData()),
toHexString(orig.getExtraData())
);
outputSummary.add(output);
}
if (!toHexString(orig.getNonce())
.equals(toHexString(valid.getNonce()))) {
String output =
String.format("wrong block.nonce: \n expected: %s \n got: %s",
toHexString(valid.getNonce()),
toHexString(orig.getNonce())
);
outputSummary.add(output);
}
return outputSummary;
}
}

View File

@ -0,0 +1,82 @@
package org.ethereum.jsontestsuite.validators;
import org.ethereum.vm.DataWord;
import org.ethereum.vm.LogInfo;
import org.spongycastle.util.encoders.Hex;
import java.util.ArrayList;
import java.util.List;
public class LogsValidator {
public static List<String> valid(List<LogInfo> origLogs, List<LogInfo> postLogs) {
List<String> results = new ArrayList<>();
int i = 0;
for (LogInfo postLog : postLogs) {
if (origLogs == null || origLogs.size() - 1 < i){
String formattedString = String.format("Log: %s: was expected but doesn't exist: address: %s",
i, Hex.toHexString(postLog.getAddress()));
results.add(formattedString);
continue;
}
LogInfo realLog = origLogs.get(i);
String postAddress = Hex.toHexString(postLog.getAddress());
String realAddress = Hex.toHexString(realLog.getAddress());
if (!postAddress.equals(realAddress)) {
String formattedString = String.format("Log: %s: has unexpected address, expected address: %s found address: %s",
i, postAddress, realAddress);
results.add(formattedString);
}
String postData = Hex.toHexString(postLog.getData());
String realData = Hex.toHexString(realLog.getData());
if (!postData.equals(realData)) {
String formattedString = String.format("Log: %s: has unexpected data, expected data: %s found data: %s",
i, postData, realData);
results.add(formattedString);
}
String postBloom = Hex.toHexString(postLog.getBloom().getData());
String realBloom = Hex.toHexString(realLog.getBloom().getData());
if (!postData.equals(realData)) {
String formattedString = String.format("Log: %s: has unexpected bloom, expected bloom: %s found bloom: %s",
i, postBloom, realBloom);
results.add(formattedString);
}
List<DataWord> postTopics = postLog.getTopics();
List<DataWord> realTopics = realLog.getTopics();
int j = 0;
for (DataWord postTopic : postTopics) {
DataWord realTopic = realTopics.get(j);
if (!postTopic.equals(realTopic)) {
String formattedString = String.format("Log: %s: has unexpected topic: %s, expected topic: %s found topic: %s",
i, j, postTopic, realTopic);
results.add(formattedString);
}
++j;
}
++i;
}
return results;
}
}

View File

@ -0,0 +1,28 @@
package org.ethereum.jsontestsuite.validators;
import org.ethereum.jsontestsuite.Utils;
import org.spongycastle.util.encoders.Hex;
import java.util.ArrayList;
import java.util.List;
import static org.ethereum.jsontestsuite.Utils.parseData;
public class OutputValidator {
public static List<String> valid(String origOutput, String postOutput){
List<String> results = new ArrayList<>();
String postOutputFormated = Hex.toHexString(parseData(postOutput));
if (!origOutput.equals(postOutputFormated)){
String formattedString = String.format("HReturn: wrong expected: %s, current: %s",
postOutputFormated, origOutput);
results.add(formattedString);
}
return results;
}
}

View File

@ -0,0 +1,68 @@
package org.ethereum.jsontestsuite.validators;
import com.google.common.collect.Sets;
import org.ethereum.core.AccountState;
import org.ethereum.db.ContractDetails;
import org.ethereum.facade.Repository;
import org.ethereum.util.ByteUtil;
import org.spongycastle.util.encoders.Hex;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import static org.ethereum.util.ByteUtil.difference;
public class RepositoryValidator {
public static List<String> valid(Repository currentRepository, Repository postRepository) {
List<String> results = new ArrayList<>();
Set<byte[]> currentKeys = currentRepository.getAccountsKeys();
Set<byte[]> expectedKeys = postRepository.getAccountsKeys();
if (expectedKeys.size() != currentKeys.size()) {
String out =
String.format("The size of the repository is invalid \n expected: %d, \n current: %d",
expectedKeys.size(), currentKeys.size());
results.add(out);
}
for (byte[] address : currentKeys) {
AccountState state = currentRepository.getAccountState(address);
ContractDetails details = currentRepository.getContractDetails(address);
AccountState postState = postRepository.getAccountState(address);
ContractDetails postDetails = postRepository.getContractDetails(address);
List<String> accountResult =
AccountValidator.valid(Hex.toHexString(address), postState, postDetails, state, details);
results.addAll(accountResult);
}
Set<byte[]> expectedButAbsent = difference(expectedKeys, currentKeys);
for (byte[] address : expectedButAbsent){
String formattedString = String.format("Account: %s: expected but doesn't exist",
Hex.toHexString(address));
results.add(formattedString);
}
// Compare roots
String postRoot = Hex.toHexString(postRepository.getRoot());
String currRoot = Hex.toHexString(currentRepository.getRoot());
if (!postRoot.equals(currRoot)){
String formattedString = String.format("Root hash don't much: expected: %s current: %s",
postRoot, currRoot);
results.add(formattedString);
}
return results;
}
}

View File

@ -0,0 +1,96 @@
package org.ethereum.net.rlpx;
import org.ethereum.crypto.ECKey;
import org.spongycastle.math.ec.ECPoint;
import org.spongycastle.util.BigIntegers;
import org.spongycastle.util.encoders.Hex;
import java.util.Arrays;
import static org.ethereum.util.ByteUtil.merge;
/**
* Authentication initiation message, to be wrapped inside
*
* Created by devrandom on 2015-04-07.
*/
public class AuthInitiateMessage {
ECKey.ECDSASignature signature; // 65 bytes
byte[] ephemeralPublicHash; // 32 bytes
ECPoint publicKey; // 64 bytes - uncompressed and no type byte
byte[] nonce; // 32 bytes
boolean isTokenUsed; // 1 byte - 0x00 or 0x01
public AuthInitiateMessage() {
}
public static int getLength() {
return 65+32+64+32+1;
}
static AuthInitiateMessage decode(byte[] wire) {
AuthInitiateMessage message = new AuthInitiateMessage();
int offset = 0;
byte[] r = new byte[32];
byte[] s = new byte[32];
System.arraycopy(wire, offset, r, 0, 32);
offset += 32;
System.arraycopy(wire, offset, s, 0, 32);
offset += 32;
int v = wire[offset] + 27;
offset += 1;
message.signature = ECKey.ECDSASignature.fromComponents(r, s, (byte)v);
message.ephemeralPublicHash = new byte[32];
System.arraycopy(wire, offset, message.ephemeralPublicHash, 0, 32);
offset += 32;
byte[] bytes = new byte[65];
System.arraycopy(wire, offset, bytes, 1, 64);
offset += 64;
bytes[0] = 0x04; // uncompressed
message.publicKey = ECKey.CURVE.getCurve().decodePoint(bytes);
message.nonce = new byte[32];
System.arraycopy(wire, offset, message.nonce, 0, 32);
offset += message.nonce.length;
byte tokenUsed = wire[offset];
offset += 1;
if (tokenUsed != 0x00 && tokenUsed != 0x01)
throw new RuntimeException("invalid boolean"); // TODO specific exception
message.isTokenUsed = (tokenUsed == 0x01);
return message;
}
public byte[] encode() {
// FIXME does this code generate a constant length for each item?
byte[] sigBytes = merge(BigIntegers.asUnsignedByteArray(signature.r),
BigIntegers.asUnsignedByteArray(signature.s), new byte[]{EncryptionHandshake.recIdFromSignatureV(signature.v)});
byte[] buffer = new byte[getLength()];
int offset = 0;
System.arraycopy(sigBytes, 0, buffer, offset, sigBytes.length);
offset += sigBytes.length;
System.arraycopy(ephemeralPublicHash, 0, buffer, offset, ephemeralPublicHash.length);
offset += ephemeralPublicHash.length;
byte[] publicBytes = publicKey.getEncoded(false);
System.arraycopy(publicBytes, 1, buffer, offset, publicBytes.length - 1);
offset += publicBytes.length - 1;
System.arraycopy(nonce, 0, buffer, offset, nonce.length);
offset += nonce.length;
buffer[offset] = (byte)(isTokenUsed ? 0x01 : 0x00);
offset += 1;
return buffer;
}
@Override
public String toString() {
byte[] sigBytes = merge(BigIntegers.asUnsignedByteArray(signature.r),
BigIntegers.asUnsignedByteArray(signature.s), new byte[]{EncryptionHandshake.recIdFromSignatureV(signature.v)});
return "AuthInitiateMessage{" +
"\n sigBytes=" + Hex.toHexString(sigBytes) +
"\n ephemeralPublicHash=" + Hex.toHexString(ephemeralPublicHash) +
"\n publicKey=" + Hex.toHexString(publicKey.getEncoded(false)) +
"\n nonce=" + Hex.toHexString(nonce) +
"\n}";
}
}

View File

@ -0,0 +1,51 @@
package org.ethereum.net.rlpx;
import org.ethereum.crypto.ECKey;
import org.spongycastle.math.ec.ECPoint;
/**
* Authentication response message, to be wrapped inside
*
* Created by devrandom on 2015-04-07.
*/
public class AuthResponseMessage {
ECPoint ephemeralPublicKey; // 64 bytes - uncompressed and no type byte
byte[] nonce; // 32 bytes
boolean isTokenUsed; // 1 byte - 0x00 or 0x01
static AuthResponseMessage decode(byte[] wire) {
int offset = 0;
AuthResponseMessage message = new AuthResponseMessage();
byte[] bytes = new byte[65];
System.arraycopy(wire, offset, bytes, 1, 64);
offset += 64;
bytes[0] = 0x04; // uncompressed
message.ephemeralPublicKey = ECKey.CURVE.getCurve().decodePoint(bytes);
message.nonce = new byte[32];
System.arraycopy(wire, offset, message.nonce, 0, 32);
offset += message.nonce.length;
byte tokenUsed = wire[offset];
offset += 1;
if (tokenUsed != 0x00 && tokenUsed != 0x01)
throw new RuntimeException("invalid boolean"); // TODO specific exception
message.isTokenUsed = (tokenUsed == 0x01);
return message;
}
public static int getLength() {
return 64+32+1;
}
public byte[] encode() {
byte[] buffer = new byte[getLength()];
int offset = 0;
byte[] publicBytes = ephemeralPublicKey.getEncoded(false);
System.arraycopy(publicBytes, 1, buffer, offset, publicBytes.length - 1);
offset += publicBytes.length - 1;
System.arraycopy(nonce, 0, buffer, offset, nonce.length);
offset += nonce.length;
buffer[offset] = (byte)(isTokenUsed ? 0x01 : 0x00);
offset += 1;
return buffer;
}
}

View File

@ -0,0 +1,228 @@
package org.ethereum.net.rlpx;
import com.google.common.base.Preconditions;
import com.google.common.base.Throwables;
import org.ethereum.crypto.ECIESCoder;
import org.ethereum.crypto.ECKey;
import org.ethereum.util.ByteUtil;
import org.spongycastle.crypto.InvalidCipherTextException;
import org.spongycastle.crypto.digests.SHA3Digest;
import org.spongycastle.math.ec.ECPoint;
import javax.annotation.Nullable;
import java.io.IOException;
import java.math.BigInteger;
import java.security.SecureRandom;
import static org.ethereum.crypto.SHA3Helper.sha3;
/**
* Created by devrandom on 2015-04-08.
*/
public class EncryptionHandshake {
public static final int NONCE_SIZE = 32;
public static final int MAC_SIZE = 256;
public static final int SECRET_SIZE = 32;
private SecureRandom random = new SecureRandom();
private boolean isInitiator;
private ECKey ephemeralKey;
private ECPoint remotePublicKey;
private ECPoint remoteEphemeralKey;
private byte[] initiatorNonce;
private byte[] responderNonce;
private Secrets secrets;
public EncryptionHandshake(ECPoint remotePublicKey) {
this.remotePublicKey = remotePublicKey;
ephemeralKey = new ECKey(random);
initiatorNonce = new byte[NONCE_SIZE];
random.nextBytes(initiatorNonce);
isInitiator = true;
}
public EncryptionHandshake() {
ephemeralKey = new ECKey(random);
responderNonce = new byte[NONCE_SIZE];
random.nextBytes(responderNonce);
isInitiator = false;
}
/**
* Create a handshake auth message
*
* @param token previous token if we had a previous session
* @param key our private key
*/
public AuthInitiateMessage createAuthInitiate(@Nullable byte[] token, ECKey key) {
AuthInitiateMessage message = new AuthInitiateMessage();
boolean isToken;
if (token == null) {
isToken = false;
BigInteger secretScalar = remotePublicKey.multiply(key.getPrivKey()).normalize().getXCoord().toBigInteger();
token = ByteUtil.bigIntegerToBytes(secretScalar, NONCE_SIZE);
} else {
isToken = true;
}
byte[] nonce = initiatorNonce;
byte[] signed = xor(token, nonce);
message.signature = ephemeralKey.sign(signed);
message.isTokenUsed = isToken;
message.ephemeralPublicHash = sha3(ephemeralKey.getPubKeyPoint().getEncoded(false), 1, 64);
message.publicKey = key.getPubKeyPoint();
message.nonce = initiatorNonce;
return message;
}
private static byte[] xor(byte[] b1, byte[] b2) {
Preconditions.checkArgument(b1.length == b2.length);
byte[] out = new byte[b1.length];
for (int i = 0; i < b1.length; i++) {
out[i] = (byte) (b1[i] ^ b2[i]);
}
return out;
}
public byte[] encryptAuthMessage(AuthInitiateMessage message) {
return ECIESCoder.encrypt(remotePublicKey, message.encode());
}
public byte[] encryptAuthReponse(AuthResponseMessage message) {
return ECIESCoder.encrypt(remotePublicKey, message.encode());
}
public AuthResponseMessage decryptAuthResponse(byte[] ciphertext, ECKey myKey) {
try {
byte[] plaintext = ECIESCoder.decrypt(myKey.getPrivKey(), ciphertext);
return AuthResponseMessage.decode(plaintext);
} catch (IOException | InvalidCipherTextException e) {
throw Throwables.propagate(e);
}
}
public AuthInitiateMessage decryptAuthInitiate(byte[] ciphertext, ECKey myKey) {
try {
byte[] plaintext = ECIESCoder.decrypt(myKey.getPrivKey(), ciphertext);
return AuthInitiateMessage.decode(plaintext);
} catch (IOException | InvalidCipherTextException e) {
throw Throwables.propagate(e);
}
}
public void handleAuthResponse(ECKey myKey, byte[] initiatePacket, byte[] responsePacket) {
AuthResponseMessage response = decryptAuthResponse(responsePacket, myKey);
remoteEphemeralKey = response.ephemeralPublicKey;
responderNonce = response.nonce;
agreeSecret(initiatePacket, responsePacket);
}
private void agreeSecret(byte[] initiatePacket, byte[] responsePacket) {
BigInteger secretScalar = remoteEphemeralKey.multiply(ephemeralKey.getPrivKey()).normalize().getXCoord().toBigInteger();
byte[] agreedSecret = ByteUtil.bigIntegerToBytes(secretScalar, SECRET_SIZE);
byte[] sharedSecret = sha3(agreedSecret, sha3(responderNonce, initiatorNonce));
byte[] aesSecret = sha3(agreedSecret, sharedSecret);
secrets = new Secrets();
secrets.aes = aesSecret;
secrets.mac = sha3(agreedSecret, aesSecret);
secrets.token = sha3(sharedSecret);
// System.out.println("mac " + Hex.toHexString(secrets.mac));
// System.out.println("aes " + Hex.toHexString(secrets.aes));
// System.out.println("shared " + Hex.toHexString(sharedSecret));
// System.out.println("ecdhe " + Hex.toHexString(agreedSecret));
SHA3Digest mac1 = new SHA3Digest(MAC_SIZE);
mac1.update(xor(secrets.mac, responderNonce), 0, secrets.mac.length);
byte[] buf = new byte[32];
new SHA3Digest(mac1).doFinal(buf, 0);
mac1.update(initiatePacket, 0, initiatePacket.length);
new SHA3Digest(mac1).doFinal(buf, 0);
SHA3Digest mac2 = new SHA3Digest(MAC_SIZE);
mac2.update(xor(secrets.mac, initiatorNonce), 0, secrets.mac.length);
new SHA3Digest(mac2).doFinal(buf, 0);
mac2.update(responsePacket, 0, responsePacket.length);
new SHA3Digest(mac2).doFinal(buf, 0);
if (isInitiator) {
secrets.egressMac = mac1;
secrets.ingressMac = mac2;
} else {
secrets.egressMac = mac2;
secrets.ingressMac = mac1;
}
}
public byte[] handleAuthInitiate(byte[] initiatePacket, ECKey key) {
AuthResponseMessage response = makeAuthInitiate(initiatePacket, key);
byte[] responsePacket = encryptAuthReponse(response);
agreeSecret(initiatePacket, responsePacket);
return responsePacket;
}
AuthResponseMessage makeAuthInitiate(byte[] initiatePacket, ECKey key) {
AuthInitiateMessage initiate = decryptAuthInitiate(initiatePacket, key);
initiatorNonce = initiate.nonce;
remotePublicKey = initiate.publicKey;
BigInteger secretScalar = remotePublicKey.multiply(key.getPrivKey()).normalize().getXCoord().toBigInteger();
byte[] token = ByteUtil.bigIntegerToBytes(secretScalar, NONCE_SIZE);
byte[] signed = xor(token, initiatorNonce);
ECKey ephemeral = ECKey.recoverFromSignature(recIdFromSignatureV(initiate.signature.v),
initiate.signature, signed, false);
if (ephemeral == null) {
throw new RuntimeException("failed to recover signatue from message");
}
remoteEphemeralKey = ephemeral.getPubKeyPoint();
AuthResponseMessage response = new AuthResponseMessage();
response.isTokenUsed = initiate.isTokenUsed;
response.ephemeralPublicKey = ephemeralKey.getPubKeyPoint();
response.nonce = responderNonce;
return response;
}
static public byte recIdFromSignatureV(int v) {
if (v >= 31) {
// compressed
v -= 4;
}
return (byte)(v - 27);
}
public Secrets getSecrets() {
return secrets;
}
public ECPoint getRemotePublicKey() {
return remotePublicKey;
}
public static class Secrets {
byte[] aes;
byte[] mac;
byte[] token;
SHA3Digest egressMac;
SHA3Digest ingressMac;
public byte[] getAes() {
return aes;
}
public byte[] getMac() {
return mac;
}
public byte[] getToken() {
return token;
}
public SHA3Digest getIngressMac() {
return ingressMac;
}
public SHA3Digest getEgressMac() {
return egressMac;
}
}
public boolean isInitiator() {
return isInitiator;
}
}

View File

@ -0,0 +1,198 @@
package org.ethereum.net.rlpx;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufInputStream;
import io.netty.buffer.ByteBufOutputStream;
import org.ethereum.util.RLP;
import org.spongycastle.crypto.StreamCipher;
import org.spongycastle.crypto.digests.SHA3Digest;
import org.spongycastle.crypto.engines.AESFastEngine;
import org.spongycastle.crypto.modes.SICBlockCipher;
import org.spongycastle.crypto.params.KeyParameter;
import org.spongycastle.crypto.params.ParametersWithIV;
import java.io.*;
/**
* Created by devrandom on 2015-04-11.
*/
public class FrameCodec {
private final StreamCipher enc;
private final StreamCipher dec;
private final SHA3Digest egressMac;
private final SHA3Digest ingressMac;
private final byte[] mac;
boolean isHeadRead;
private int totalBodySize;
public FrameCodec(EncryptionHandshake.Secrets secrets) {
this.mac = secrets.mac;
int blockSize = secrets.aes.length * 8;
enc = new SICBlockCipher(new AESFastEngine());
enc.init(true, new ParametersWithIV(new KeyParameter(secrets.aes), new byte[blockSize / 8]));
dec = new SICBlockCipher(new AESFastEngine());
dec.init(false, new ParametersWithIV(new KeyParameter(secrets.aes), new byte[blockSize / 8]));
egressMac = secrets.egressMac;
ingressMac = secrets.ingressMac;
}
private AESFastEngine makeMacCipher() {
// Stateless AES encryption
AESFastEngine macc = new AESFastEngine();
macc.init(true, new KeyParameter(mac));
return macc;
}
public static class Frame {
long type;
int size;
InputStream payload;
public Frame(long type, int size, InputStream payload) {
this.type = type;
this.size = size;
this.payload = payload;
}
public Frame(int type, byte[] payload) {
this.type = type;
this.size = payload.length;
this.payload = new ByteArrayInputStream(payload);
}
public int getSize() {
return size;
}
public long getType() {return type;}
public InputStream getStream() {
return payload;
}
}
public void writeFrame(Frame frame, ByteBuf buf) throws IOException {
writeFrame(frame, new ByteBufOutputStream(buf));
}
public void writeFrame(Frame frame, OutputStream out) throws IOException {
byte[] headBuffer = new byte[32];
byte[] ptype = RLP.encodeInt((int) frame.type); // FIXME encodeLong
int totalSize = frame.size + ptype.length;
headBuffer[0] = (byte)(totalSize >> 16);
headBuffer[1] = (byte)(totalSize >> 8);
headBuffer[2] = (byte)(totalSize);
enc.processBytes(headBuffer, 0, 16, headBuffer, 0);
// Header MAC
updateMac(egressMac, headBuffer, 0, headBuffer, 16, true);
byte[] buff = new byte[256];
out.write(headBuffer);
enc.processBytes(ptype, 0, ptype.length, buff, 0);
out.write(buff, 0, ptype.length);
egressMac.update(buff, 0, ptype.length);
while (true) {
int n = frame.payload.read(buff);
if (n <= 0) break;
enc.processBytes(buff, 0, n, buff, 0);
egressMac.update(buff, 0, n);
out.write(buff, 0, n);
}
int padding = 16 - (totalSize % 16);
byte[] pad = new byte[16];
if (padding < 16) {
enc.processBytes(pad, 0, padding, buff, 0);
egressMac.update(buff, 0, padding);
out.write(buff, 0, padding);
}
// Frame MAC
byte[] macBuffer = new byte[egressMac.getDigestSize()];
doSum(egressMac, macBuffer); // fmacseed
updateMac(egressMac, macBuffer, 0, macBuffer, 0, true);
out.write(macBuffer, 0, 16);
}
public Frame readFrame(ByteBuf buf) throws IOException {
return readFrame(new ByteBufInputStream(buf));
}
public Frame readFrame(DataInput inp) throws IOException {
if (!isHeadRead) {
byte[] headBuffer = new byte[32];
try {
inp.readFully(headBuffer);
} catch (EOFException e) {
return null;
}
// Header MAC
updateMac(ingressMac, headBuffer, 0, headBuffer, 16, false);
dec.processBytes(headBuffer, 0, 16, headBuffer, 0);
totalBodySize = headBuffer[0];
totalBodySize = (totalBodySize << 8) + (headBuffer[1] & 0xFF);
totalBodySize = (totalBodySize << 8) + (headBuffer[2] & 0xFF);
isHeadRead = true;
}
int padding = 16 - (totalBodySize % 16);
if (padding == 16) padding = 0;
int macSize = 16;
byte[] buffer = new byte[totalBodySize + padding + macSize];
try {
inp.readFully(buffer);
} catch (EOFException e) {
return null;
}
int frameSize = buffer.length - macSize;
ingressMac.update(buffer, 0, frameSize);
dec.processBytes(buffer, 0, frameSize, buffer, 0);
int pos = 0;
long type = RLP.decodeInt(buffer, pos); // FIXME long
pos = RLP.getNextElementIndex(buffer, pos);
InputStream payload = new ByteArrayInputStream(buffer, pos, totalBodySize - pos);
int size = totalBodySize - pos;
byte[] macBuffer = new byte[ingressMac.getDigestSize()];
// Frame MAC
doSum(ingressMac, macBuffer); // fmacseed
updateMac(ingressMac, macBuffer, 0, buffer, frameSize, false);
isHeadRead = false;
return new Frame(type, size, payload);
}
private byte[] updateMac(SHA3Digest mac, byte[] seed, int offset, byte[] out, int outOffset, boolean egress) throws IOException {
byte[] aesBlock = new byte[mac.getDigestSize()];
doSum(mac, aesBlock);
makeMacCipher().processBlock(aesBlock, 0, aesBlock, 0);
// Note that although the mac digest size is 32 bytes, we only use 16 bytes in the computation
int length = 16;
for (int i = 0; i < length; i++) {
aesBlock[i] ^= seed[i + offset];
}
mac.update(aesBlock, 0, length);
byte[] result = new byte[mac.getDigestSize()];
doSum(mac, result);
if (egress) {
for (int i = 0; i < length; i++) {
out[i + outOffset] = result[i];
}
} else {
for (int i = 0; i < length; i++) {
if (out[i + outOffset] != result[i]) {
throw new IOException("MAC mismatch");
}
}
}
return result;
}
private void doSum(SHA3Digest mac, byte[] out) {
// doFinal without resetting the MAC by using clone of digest state
new SHA3Digest(mac).doFinal(out, 0);
}
}

View File

@ -0,0 +1,73 @@
package org.ethereum.net.rlpx;
import com.google.common.collect.Lists;
import org.ethereum.net.client.Capability;
import org.ethereum.util.*;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.List;
import static org.ethereum.util.ByteUtil.longToBytes;
/**
* Created by devrandom on 2015-04-12.
*/
public class HandshakeMessage {
public static final int HANDSHAKE_MESSAGE_TYPE = 0x00;
long version;
String name;
List<Capability> caps;
long listenPort;
byte[] nodeId;
public static final int NODE_ID_BITS = 512;
public HandshakeMessage(long version, String name, List<Capability> caps, long listenPort, byte[] nodeId) {
this.version = version;
this.name = name;
this.caps = caps;
this.listenPort = listenPort;
this.nodeId = nodeId;
}
HandshakeMessage() {
}
static HandshakeMessage parse(byte[] wire) {
RLPList list = (RLPList) RLP.decode2(wire).get(0);
HandshakeMessage message = new HandshakeMessage();
Iterator<RLPElement> iter = list.iterator();
message.version = ByteUtil.byteArrayToInt(iter.next().getRLPData()); // FIXME long
message.name = new String(iter.next().getRLPData(), Charset.forName("UTF-8"));
// caps
message.caps = Lists.newArrayList();
for (RLPElement capEl : (RLPList)iter.next()) {
RLPList capElList = (RLPList)capEl;
String name = new String(capElList.get(0).getRLPData(), Charset.forName("UTF-8"));
long version = ByteUtil.byteArrayToInt(capElList.get(1).getRLPData());
message.caps.add(new Capability(name, (byte)version)); // FIXME long
}
message.listenPort = ByteUtil.byteArrayToInt(iter.next().getRLPData());
message.nodeId = iter.next().getRLPData();
return message;
}
public byte[] encode() {
List<byte[]> capsItemBytes = Lists.newArrayList();
for (Capability cap : caps) {
capsItemBytes.add(RLP.encodeList(
RLP.encodeElement(cap.getName().getBytes()),
RLP.encodeElement(ByteUtil.stripLeadingZeroes(longToBytes(cap.getVersion())))
));
}
return RLP.encodeList(
RLP.encodeElement(ByteUtil.stripLeadingZeroes(longToBytes(version))),
RLP.encodeElement(name.getBytes()),
RLP.encodeList(capsItemBytes.toArray(new byte[0][])),
RLP.encodeElement(ByteUtil.stripLeadingZeroes(longToBytes(listenPort))),
RLP.encodeElement(nodeId)
);
}
}

View File

@ -0,0 +1,132 @@
package org.ethereum.net.rlpx;
import com.google.common.base.Throwables;
import com.google.common.collect.Lists;
import org.ethereum.crypto.ECIESCoder;
import org.ethereum.crypto.ECKey;
import org.ethereum.net.client.Capability;
import org.ethereum.net.message.ReasonCode;
import org.ethereum.net.p2p.DisconnectMessage;
import org.ethereum.net.p2p.PingMessage;
import org.ethereum.net.rlpx.EncryptionHandshake.Secrets;
import org.spongycastle.crypto.digests.SHA3Digest;
import org.spongycastle.math.ec.ECPoint;
import org.spongycastle.util.encoders.Hex;
import java.io.*;
import java.net.Socket;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;
/**
* Created by devrandom on 2015-04-09.
*/
public class Handshaker {
private final ECKey myKey;
private final byte[] nodeId;
private Secrets secrets;
public static void main(String[] args) throws IOException, URISyntaxException {
URI uri = new URI(args[0]);
if (!uri.getScheme().equals("enode"))
throw new RuntimeException("expecting URL in the format enode://PUBKEY@HOST:PORT");
new Handshaker().doHandshake(uri.getHost(), uri.getPort(), uri.getUserInfo());
}
public Handshaker() {
myKey = new ECKey().decompress();
byte[] nodeIdWithFormat = myKey.getPubKey();
nodeId = new byte[nodeIdWithFormat.length - 1];
System.arraycopy(nodeIdWithFormat, 1, nodeId, 0, nodeId.length);
System.out.println("Node ID " + Hex.toHexString(nodeId));
}
/**
* Sample output:
* <pre>
Node ID b7fb52ddb1f269fef971781b9568ad65d30ac3b6055ebd6a0a762e6b67a7c92bd7c1fdf3c7c722d65ae70bfe6a9a58443297485aa29e3acd9bdf2ee0df4f5c45
packet f86b0399476574682f76302e392e372f6c696e75782f676f312e342e32ccc5836574683cc5837368680280b840f1c041a7737e8e06536d9defb92cb3db6ecfeb1b1208edfca6953c0c683a31ff0a478a832bebb6629e4f5c13136478842cc87a007729f3f1376f4462eb424ded
[eth:60, shh:2]
packet type 16
packet f8453c7b80a0fd4af92a79c7fc2fd8bf0d342f2e832e1d4f485c85b9152d2039e03bc604fdcaa0fd4af92a79c7fc2fd8bf0d342f2e832e1d4f485c85b9152d2039e03bc604fdca
packet type 24
packet c102
packet type 3
packet c0
packet type 1
packet c180
</pre>
*/
public void doHandshake(String host, int port, String remoteIdHex) throws IOException {
byte[] remoteId = Hex.decode(remoteIdHex);
byte[] remotePublicBytes = new byte[remoteId.length + 1];
System.arraycopy(remoteId, 0, remotePublicBytes, 1, remoteId.length);
remotePublicBytes[0] = 0x04; // uncompressed
ECPoint remotePublic = ECKey.fromPublicOnly(remotePublicBytes).getPubKeyPoint();
Socket sock = new Socket(host, port);
InputStream inp = sock.getInputStream();
OutputStream out = sock.getOutputStream();
EncryptionHandshake initiator = new EncryptionHandshake(remotePublic);
AuthInitiateMessage initiateMessage = initiator.createAuthInitiate(null, myKey);
byte[] initiatePacket = initiator.encryptAuthMessage(initiateMessage);
out.write(initiatePacket);
byte[] responsePacket = new byte[AuthResponseMessage.getLength() + ECIESCoder.getOverhead()];
int n = inp.read(responsePacket);
if (n < responsePacket.length)
throw new IOException("could not read, got " + n);
initiator.handleAuthResponse(myKey, initiatePacket, responsePacket);
byte[] buf = new byte[initiator.getSecrets().getEgressMac().getDigestSize()];
new SHA3Digest(initiator.getSecrets().getEgressMac()).doFinal(buf, 0);
new SHA3Digest(initiator.getSecrets().getIngressMac()).doFinal(buf, 0);
RlpxConnection conn = new RlpxConnection(initiator.getSecrets(), inp, out);
HandshakeMessage handshakeMessage = new HandshakeMessage(
3,
"computronium1",
Lists.newArrayList(
new Capability("eth", (byte) 60),
new Capability("shh", (byte) 2)
),
3333,
nodeId
);
conn.sendProtocolHandshake(handshakeMessage);
conn.handleNextMessage();
if (!Arrays.equals(remoteId, conn.getHandshakeMessage().nodeId))
throw new IOException("returns node ID doesn't match the node ID we dialed to");
System.out.println(conn.getHandshakeMessage().caps);
conn.writeMessage(new PingMessage());
conn.writeMessage(new DisconnectMessage(ReasonCode.PEER_QUITING));
conn.handleNextMessage();
while (true) {
try {
conn.handleNextMessage();
} catch (EOFException e) {
break;
}
}
this.secrets = initiator.getSecrets();
}
public Secrets getSecrets() {
return secrets;
}
private void delay(int millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
Throwables.propagate(e);
}
}
}

View File

@ -0,0 +1,56 @@
package org.ethereum.net.rlpx;
import org.ethereum.net.p2p.P2pMessage;
import org.spongycastle.util.encoders.Hex;
import java.io.*;
/**
* Created by devrandom on 2015-04-12.
*/
public class RlpxConnection {
private final EncryptionHandshake.Secrets secrets;
private final FrameCodec codec;
private final DataInputStream inp;
private final OutputStream out;
private HandshakeMessage handshakeMessage;
public RlpxConnection(EncryptionHandshake.Secrets secrets, InputStream inp, OutputStream out) {
this.secrets = secrets;
this.inp = new DataInputStream(inp);
this.out = out;
this.codec = new FrameCodec(secrets);
}
public void sendProtocolHandshake(HandshakeMessage message) throws IOException {
byte[] payload = message.encode();
codec.writeFrame(new FrameCodec.Frame(HandshakeMessage.HANDSHAKE_MESSAGE_TYPE, payload), out);
}
public void handleNextMessage() throws IOException {
FrameCodec.Frame frame = codec.readFrame(inp);
if (handshakeMessage == null) {
if (frame.type != HandshakeMessage.HANDSHAKE_MESSAGE_TYPE)
throw new IOException("expected handshake or disconnect");
// TODO handle disconnect
byte[] wire = new byte[frame.size];
frame.payload.read(wire);
System.out.println("packet " + Hex.toHexString(wire));
handshakeMessage = HandshakeMessage.parse(wire);
} else {
System.out.println("packet type " + frame.type);
byte[] wire = new byte[frame.size];
frame.payload.read(wire);
System.out.println("packet " + Hex.toHexString(wire));
}
}
public HandshakeMessage getHandshakeMessage() {
return handshakeMessage;
}
public void writeMessage(P2pMessage message) throws IOException {
byte[] payload = message.getEncoded();
codec.writeFrame(new FrameCodec.Frame(message.getCommand().asByte(), payload), out);
}
}

View File

@ -0,0 +1,220 @@
package org.ethereum.net.wire;
import com.google.common.io.ByteStreams;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.ByteToMessageCodec;
import org.ethereum.crypto.ECIESCoder;
import org.ethereum.crypto.ECKey;
import org.ethereum.listener.EthereumListener;
import org.ethereum.manager.WorldManager;
import org.ethereum.net.eth.EthMessageCodes;
import org.ethereum.net.message.Message;
import org.ethereum.net.message.MessageFactory;
import org.ethereum.net.p2p.HelloMessage;
import org.ethereum.net.p2p.P2pMessageCodes;
import org.ethereum.net.rlpx.*;
import org.ethereum.net.server.Channel;
import org.ethereum.net.shh.ShhMessageCodes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongycastle.math.ec.ECPoint;
import org.spongycastle.util.encoders.Hex;
import org.springframework.beans.factory.annotation.Autowired;
//import org.springframework.context.annotation.Scope;
//import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.List;
import static org.ethereum.net.rlpx.FrameCodec.Frame;
/**
* The PacketDecoder parses every valid Ethereum packet to a Message object
*/
//@Component
//@Scope("prototype")
public class MessageCodec extends ByteToMessageCodec<Message> {
private static final Logger loggerWire = LoggerFactory.getLogger("wire");
private static final Logger loggerNet = LoggerFactory.getLogger("net");
private FrameCodec frameCodec;
private ECKey myKey;
private byte[] nodeId;
private byte[] remoteId;
private EncryptionHandshake handshake;
private byte[] initiatePacket;
private Channel channel;
private boolean isHandshakeDone;
private final InitiateHandler initiator = new InitiateHandler();
public InitiateHandler getInitiator() {
return initiator;
}
public class InitiateHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
initiate(ctx);
}
}
@Autowired
WorldManager worldManager;
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
if (!isHandshakeDone)
decodeHandshake(ctx, in);
else
decodeMessage(ctx, in, out);
}
private void decodeMessage(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws IOException {
if (in.readableBytes() == 0) return;
Frame frame = null;
frame = frameCodec.readFrame(in);
// Check if a full frame was available. If not, we'll try later when more bytes come in.
if (frame == null) return;
byte[] payload = ByteStreams.toByteArray(frame.getStream());
if (loggerWire.isDebugEnabled())
loggerWire.debug("Encoded: [{}]", Hex.toHexString(payload));
Message msg = MessageFactory.createMessage((byte) frame.getType(), payload);
if (loggerNet.isInfoEnabled())
loggerNet.info("From: \t{} \tRecv: \t{}", ctx.channel().remoteAddress(), msg);
EthereumListener listener = worldManager.getListener();
listener.onRecvMessage(msg);
out.add(msg);
}
@Override
protected void encode(ChannelHandlerContext ctx, Message msg, ByteBuf out) throws Exception {
String output = String.format("To: \t%s \tSend: \t%s", ctx.channel().remoteAddress(), msg);
worldManager.getListener().trace(output);
if (loggerNet.isInfoEnabled())
loggerNet.info("To: \t{} \tSend: \t{}", ctx.channel().remoteAddress(), msg);
byte[] encoded = msg.getEncoded();
if (loggerWire.isDebugEnabled())
loggerWire.debug("Encoded: [{}]", Hex.toHexString(encoded));
/* HERE WE ACTUALLY USING THE SECRET ENCODING */
byte code = getCode(msg.getCommand());
Frame frame = new Frame(code, msg.getEncoded());
frameCodec.writeFrame(frame, out);
}
public void initiate(ChannelHandlerContext ctx) throws Exception {
loggerNet.info("RLPX protocol activated");
myKey = new ECKey().decompress();
byte[] nodeIdWithFormat = myKey.getPubKey();
nodeId = new byte[nodeIdWithFormat.length - 1];
System.arraycopy(nodeIdWithFormat, 1, nodeId, 0, nodeId.length);
byte[] remotePublicBytes = new byte[remoteId.length + 1];
System.arraycopy(remoteId, 0, remotePublicBytes, 1, remoteId.length);
remotePublicBytes[0] = 0x04; // uncompressed
ECPoint remotePublic = ECKey.fromPublicOnly(remotePublicBytes).getPubKeyPoint();
handshake = new EncryptionHandshake(remotePublic);
AuthInitiateMessage initiateMessage = handshake.createAuthInitiate(null, myKey);
initiatePacket = handshake.encryptAuthMessage(initiateMessage);
final ByteBuf byteBufMsg = ctx.alloc().buffer(initiatePacket.length);
byteBufMsg.writeBytes(initiatePacket);
ctx.writeAndFlush(byteBufMsg).sync();
if (loggerNet.isInfoEnabled())
loggerNet.info("To: \t{} \tSend: \t{}", ctx.channel().remoteAddress(), initiateMessage);
}
// consume handshake, producing no resulting message to upper layers
private void decodeHandshake(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception {
if (handshake.isInitiator()) {
if (frameCodec == null) {
byte[] responsePacket = new byte[AuthResponseMessage.getLength() + ECIESCoder.getOverhead()];
if (!buffer.isReadable(responsePacket.length))
return;
buffer.readBytes(responsePacket);
this.handshake.handleAuthResponse(myKey, initiatePacket, responsePacket);
EncryptionHandshake.Secrets secrets = this.handshake.getSecrets();
this.frameCodec = new FrameCodec(secrets);
loggerNet.info("auth exchange done");
channel.sendHelloMessage(ctx, frameCodec, Hex.toHexString(nodeId));
} else {
Frame frame = frameCodec.readFrame(buffer);
if (frame == null)
return;
byte[] payload = ByteStreams.toByteArray(frame.getStream());
HelloMessage helloMessage = new HelloMessage(payload);
if (loggerNet.isInfoEnabled())
loggerNet.info("From: \t{} \tRecv: \t{}", ctx.channel().remoteAddress(), helloMessage);
isHandshakeDone = true;
this.channel.publicRLPxHandshakeFinished(ctx, frameCodec, helloMessage, nodeId);
}
} else {
if (frameCodec == null) {
// Respond to auth
throw new UnsupportedOperationException();
} else {
Frame frame = frameCodec.readFrame(buffer);
if (frame == null)
return;
byte[] payload = ByteStreams.toByteArray(frame.getStream());
HelloMessage helloMessage = new HelloMessage(payload);
System.out.println("hello message received");
// Secret authentication finish here
isHandshakeDone = true;
channel.sendHelloMessage(ctx, frameCodec, Hex.toHexString(nodeId));
this.channel.publicRLPxHandshakeFinished(ctx, frameCodec, helloMessage, nodeId);
}
}
}
/* TODO: this dirty hack is here cause we need to use message
TODO: adaptive id on high message abstraction level,
TODO: need a solution here*/
private byte getCode(Enum msgCommand){
byte code = 0;
if (msgCommand instanceof P2pMessageCodes){
code = ((P2pMessageCodes)msgCommand).asByte();
}
if (msgCommand instanceof EthMessageCodes){
code = ((EthMessageCodes)msgCommand).asByte();
}
if (msgCommand instanceof ShhMessageCodes){
code = ((ShhMessageCodes)msgCommand).asByte();
}
return code;
}
public void setRemoteId(String remoteId, Channel channel){
this.remoteId = Hex.decode(remoteId);
this.channel = channel;
}
}

View File

@ -1,92 +0,0 @@
package org.ethereum.net.wire;
import org.ethereum.listener.EthereumListener;
import org.ethereum.manager.WorldManager;
import org.ethereum.net.message.Message;
import org.ethereum.net.message.MessageFactory;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongycastle.util.encoders.Hex;
import org.springframework.beans.factory.annotation.Autowired;
//import org.springframework.context.annotation.Scope;
//import org.springframework.stereotype.Component;
import java.util.List;
/**
* The PacketDecoder parses every valid Ethereum packet to a Message object
*/
//@Component
//@Scope("prototype")
public class MessageDecoder extends ByteToMessageDecoder {
private static final Logger loggerWire = LoggerFactory.getLogger("wire");
private static final Logger loggerNet = LoggerFactory.getLogger("net");
@Autowired
WorldManager worldManager;
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
if (!isValidEthereumPacket(in)) {
return;
}
byte[] encoded = new byte[in.readInt()];
in.readBytes(encoded);
if (loggerWire.isDebugEnabled())
loggerWire.debug("Encoded: [{}]", Hex.toHexString(encoded));
Message msg = MessageFactory.createMessage(encoded);
if (loggerNet.isInfoEnabled())
loggerNet.info("From: \t{} \tRecv: \t{}", ctx.channel().remoteAddress(), msg);
EthereumListener listener = worldManager.getListener();
listener.onRecvMessage(msg);
out.add(msg);
in.markReaderIndex();
}
private boolean isValidEthereumPacket(ByteBuf in) {
// Ethereum message is at least 8 bytes
if (in.readableBytes() < 8)
return false;
long syncToken = in.readUnsignedInt();
if (!((syncToken >> 24 & 0xFF) == 0x22 &&
(syncToken >> 16 & 0xFF) == 0x40 &&
(syncToken >> 8 & 0xFF) == 0x08 &&
(syncToken & 0xFF) == 0x91)) {
// TODO: Drop frame and continue.
// A collision can happen (although rare)
// If this happens too often, it's an attack.
// In that case, drop the peer.
loggerWire.error("Abandon garbage, wrong sync token: [{}]", syncToken);
}
// Don't have the full message yet
long msgSize = in.getInt(in.readerIndex());
if (msgSize > in.readableBytes()) {
//loggerWire.trace("msg decode: magicBytes: [{}], readBytes: [{}] / msgSize: [{}] ",
// syncToken, in.readableBytes(), msgSize);
in.resetReaderIndex();
return false;
}
loggerWire.trace("Message fully constructed: readBytes: [{}] / msgSize: [{}]", in.readableBytes(), msgSize);
return true;
}
}

View File

@ -1,53 +0,0 @@
package org.ethereum.net.wire;
import org.ethereum.manager.WorldManager;
import org.ethereum.net.message.Message;
import org.ethereum.net.message.StaticMessages;
import org.ethereum.util.ByteUtil;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongycastle.util.encoders.Hex;
import org.springframework.beans.factory.annotation.Autowired;
//import org.springframework.context.annotation.Scope;
//import org.springframework.stereotype.Component;
/**
* The PacketEncoder encodes the message and adds a sync token to every packet.
*/
//@Component
//@Scope("prototype")
public class MessageEncoder extends MessageToByteEncoder<Message> {
private static final Logger loggerWire = LoggerFactory.getLogger("wire");
private static final Logger loggerNet = LoggerFactory.getLogger("net");
@Autowired
WorldManager worldManager;
@Override
protected void encode(ChannelHandlerContext ctx, Message msg, ByteBuf out) throws Exception {
String output = String.format("To: \t%s \tSend: \t%s", ctx.channel().remoteAddress(), msg);
worldManager.getListener().trace(output);
if (loggerNet.isInfoEnabled())
loggerNet.info("To: \t{} \tSend: \t{}", ctx.channel().remoteAddress(), msg);
byte[] encoded = msg.getEncoded();
if (loggerWire.isDebugEnabled())
loggerWire.debug("Encoded: [{}]", Hex.toHexString(encoded));
out.capacity(encoded.length + 8);
out.writeBytes(StaticMessages.SYNC_TOKEN);
out.writeBytes(ByteUtil.calcPacketLength(encoded));
out.writeBytes(encoded);
}
}

View File

@ -0,0 +1,90 @@
package org.ethereum.tck;
import org.ethereum.jsontestsuite.*;
import org.ethereum.jsontestsuite.runners.StateTestRunner;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class RunTck {
private static Logger logger = LoggerFactory.getLogger("TCK-Test");
public static void main(String[] args) throws ParseException, IOException {
if (args.length > 0){
if (args[0].equals("filerun")) {
logger.info("TCK Running, file: " + args[1]);
runTest(args[1]);
} else if ((args[0].equals("content"))) {
logger.debug("TCK Running, content: ");
runContentTest(args[1].replaceAll("'", "\""));
}
} else {
logger.info("No test case specified");
}
}
public static void runContentTest(String content) throws ParseException, IOException {
Map<String, Boolean> summary = new HashMap<>();
JSONParser parser = new JSONParser();
JSONObject testSuiteObj = (JSONObject) parser.parse(content);
StateTestSuite stateTestSuite = new StateTestSuite(testSuiteObj.toJSONString());
Map<String, StateTestCase> testCases = stateTestSuite.getTestCases();
for (String testName : testCases.keySet()) {
logger.info(" Test case: {}", testName);
StateTestCase stateTestCase = testCases.get(testName);
List<String> result = StateTestRunner.run(stateTestCase);
if (!result.isEmpty())
summary.put(testName, false);
else
summary.put(testName, true);
}
logger.info("Summary: ");
logger.info("=========");
int fails = 0; int pass = 0;
for (String key : summary.keySet()){
if (summary.get(key)) ++pass; else ++fails;
String sumTest = String.format("%-60s:^%s", key, (summary.get(key) ? "PASS" : "FAIL")).
replace(' ', '.').
replace("^", " ");
logger.info(sumTest);
}
logger.info(" - Total: Pass: {}, Failed: {} - ", pass, fails);
if (fails > 0)
System.exit(1);
else
System.exit(0);
}
public static void runTest(String name) throws ParseException, IOException {
String testCaseJson = JSONReader.getFromLocal(name);
runContentTest(testCaseJson);
}
}

View File

@ -0,0 +1,82 @@
package org.ethereum.trie;
import org.ethereum.crypto.SHA3Helper;
import org.ethereum.datasource.KeyValueDataSource;
import static org.ethereum.crypto.SHA3Helper.sha3;
public class FatTrie implements Trie{
TrieImpl origTrie;
SecureTrie secureTrie;
public FatTrie() {
origTrie = new TrieImpl(null);
secureTrie = new SecureTrie(null);
}
public FatTrie(KeyValueDataSource origTrieDS, KeyValueDataSource secureTrieDS) {
origTrie = new TrieImpl(origTrieDS);
secureTrie = new SecureTrie(secureTrieDS);
}
public TrieImpl getOrigTrie() {
return origTrie;
}
public SecureTrie getSecureTrie() {
return secureTrie;
}
@Override
public byte[] get(byte[] key) {
return secureTrie.get(key);
}
@Override
public void update(byte[] key, byte[] value) {
origTrie.update(key, value);
secureTrie.update(key, value);
}
@Override
public void delete(byte[] key) {
origTrie.delete(key);
secureTrie.delete(key);
}
@Override
public byte[] getRootHash() {
return secureTrie.getRootHash();
}
@Override
public void setRoot(byte[] root) {
secureTrie.setRoot(root);
// throw new UnsupportedOperationException("Fat trie doesn't support root rollbacks");
}
@Override
public void sync() {
origTrie.sync();
secureTrie.sync();
}
@Override
public void undo() {
origTrie.undo();
secureTrie.undo();
}
@Override
public String getTrieDump() {
return secureTrie.getTrieDump();
}
@Override
public boolean validate() {
return secureTrie.validate();
}
}

View File

@ -0,0 +1,66 @@
package org.ethereum.trie;
import org.ethereum.crypto.SHA3Helper;
import org.ethereum.datasource.KeyValueDataSource;
import org.spongycastle.util.encoders.Hex;
import static org.ethereum.crypto.SHA3Helper.sha3;
import static org.ethereum.util.ByteUtil.EMPTY_BYTE_ARRAY;
public class SecureTrie extends TrieImpl implements Trie{
public SecureTrie(KeyValueDataSource db) {
super(db, "");
}
public SecureTrie(KeyValueDataSource db, Object root) {
super(db, root);
}
@Override
public byte[] get(byte[] key) {
return super.get(sha3(key));
}
@Override
public void update(byte[] key, byte[] value) {
super.update(sha3(key), value);
}
@Override
public void delete(byte[] key) {
this.update(key, EMPTY_BYTE_ARRAY);
}
@Override
public byte[] getRootHash() {
return super.getRootHash();
}
@Override
public void setRoot(byte[] root) {
super.setRoot(root);
}
@Override
public void sync() {
super.sync();
}
@Override
public void undo() {
super.undo();
}
@Override
public String getTrieDump() {
return super.getTrieDump();
}
@Override
public boolean validate() {
return super.validate();
}
}

View File

@ -0,0 +1,104 @@
package org.ethereum.util;
import org.ethereum.facade.Repository;
import java.math.BigInteger;
public class BIUtil {
/**
* @param value - not null
* @return true - if the param is zero
*/
public static boolean isZero(BigInteger value){
return value.compareTo(BigInteger.ZERO) == 0;
}
/**
* @param valueA - not null
* @param valueB - not null
* @return true - if the valueA is equal to valueB is zero
*/
public static boolean isEqual(BigInteger valueA, BigInteger valueB){
return valueA.compareTo(valueB) == 0;
}
/**
* @param valueA - not null
* @param valueB - not null
* @return true - if the valueA is not equal to valueB is zero
*/
public static boolean isNotEqual(BigInteger valueA, BigInteger valueB){
return !isEqual(valueA, valueB);
}
/**
* @param valueA - not null
* @param valueB - not null
* @return true - if the valueA is less than valueB is zero
*/
public static boolean isLessThan(BigInteger valueA, BigInteger valueB){
return valueA.compareTo(valueB) == -1;
}
/**
* @param valueA - not null
* @param valueB - not null
* @return true - if the valueA is more than valueB is zero
*/
public static boolean isMoreThan(BigInteger valueA, BigInteger valueB){
return valueA.compareTo(valueB) == 1;
}
/**
* @param valueA - not null
* @param valueB - not null
* @return sum - valueA + valueB
*/
public static BigInteger sum(BigInteger valueA, BigInteger valueB){
return valueA.add(valueB);
}
/**
* @param data = not null
* @return new positive BigInteger
*/
public static BigInteger toBI(byte[] data){
return new BigInteger(1, data);
}
/**
* @param data = not null
* @return new positive BigInteger
*/
public static BigInteger toBI(long data){
return BigInteger.valueOf(data);
}
public static boolean isPositive(BigInteger value){
return value.signum() > 0;
}
public static boolean isCovers(BigInteger covers, BigInteger value){
return covers.compareTo(value) > -1;
}
public static boolean isNotCovers(BigInteger covers, BigInteger value){
return !(covers.compareTo(value) > -1);
}
public static void transfer(Repository repository, byte[] fromAddr, byte[] toAddr, BigInteger value){
repository.addBalance(fromAddr, value.negate());
repository.addBalance(toAddr, value);
}
public static boolean exitLong(BigInteger value){
return (value.compareTo(new BigInteger(Long.MAX_VALUE + ""))) > -1;
}
}

View File

@ -7,7 +7,7 @@ peer.discovery.ip.list = poc-7.ethdev.com:30303,\
# Peer Server Zero (poc-7.ethdev.com)
peer.active.ip = poc-8.ethdev.com
peer.active.ip = poc-9.ethdev.com
peer.active.port = 30303
# Peer for server to listen for incoming