Added missing files from previous commit. Added Android-studio-material ui.
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package org.ethereum.ethereum_android;
|
||||
|
||||
public interface NavigationDrawerCallbacks {
|
||||
void onNavigationDrawerItemSelected(int position);
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 161 B |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 212 B |
After Width: | Height: | Size: 142 B |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 163 B |
After Width: | Height: | Size: 174 B |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 232 B |
After Width: | Height: | Size: 208 B |
After Width: | Height: | Size: 202 B |
After Width: | Height: | Size: 329 B |
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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" />
|
|
@ -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" />
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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}"
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package org.ethereum.core;
|
||||
|
||||
public enum ImportResult {
|
||||
SUCCESS,
|
||||
EXIST,
|
||||
NO_PARENT
|
||||
}
|
|
@ -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() {
|
||||
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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 +
|
||||
'}';
|
||||
}
|
||||
}
|
|
@ -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 +
|
||||
'}';
|
||||
}
|
||||
}
|
|
@ -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 + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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 +
|
||||
'}';
|
||||
}
|
||||
}
|
|
@ -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 + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
|
@ -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 + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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 + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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}";
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|