Gav Wood
11 years ago
74 changed files with 3096 additions and 2560 deletions
@ -0,0 +1,55 @@ |
|||||
|
CURRENT_SOURCE_DIR=$1 |
||||
|
CURRENT_BINARY_DIR=$2 |
||||
|
BUILD_TYPE=$3 |
||||
|
BUILD_PLATFORM=$4 |
||||
|
|
||||
|
echo "Current source dir: $CURRENT_SOURCE_DIR" |
||||
|
echo "Current binary dir: $CURRENT_BINARY_DIR" |
||||
|
echo "Build type: $BUILD_TYPE" |
||||
|
echo "Build platform: $BUILD_PLATFORM" |
||||
|
|
||||
|
if [[ -e "$CURRENT_SOURCE_DIR/BuildInfo.h" ]] |
||||
|
then |
||||
|
echo "Using existing BuildInfo.h" |
||||
|
cp $CURRENT_SOURCE_DIR/BuildInfo.h $CURRENT_BINARY_DIR/BuildInfo.h.tmp |
||||
|
else |
||||
|
if [[ -e "$CURRENT_SOURCE_DIR/.git" ]] |
||||
|
then |
||||
|
ETH_COMMIT_HASH=$(git --git-dir=$CURRENT_SOURCE_DIR/.git --work-tree=$CURRENT_SOURCE_DIR rev-parse HEAD) |
||||
|
ETH_LOCAL_CHANGES=$(git --git-dir=$CURRENT_SOURCE_DIR/.git --work-tree=$CURRENT_SOURCE_DIR diff --shortstat) |
||||
|
if [[ -z "$ETH_LOCAL_CHANGES" ]] |
||||
|
then |
||||
|
ETH_CLEAN_REPO=1 |
||||
|
else |
||||
|
ETH_CLEAN_REPO=0 |
||||
|
fi |
||||
|
|
||||
|
echo "Commit hash: ${ETH_COMMIT_HASH} (Clean: ${ETH_CLEAN_REPO} - ${ETH_LOCAL_CHANGES})" |
||||
|
else |
||||
|
echo "Unknown repo." |
||||
|
ETH_COMMIT_HASH=0 |
||||
|
ETH_CLEAN_REPO=1 |
||||
|
fi |
||||
|
|
||||
|
echo "// This file was automatically generated by cmake" > $CURRENT_BINARY_DIR/BuildInfo.h.tmp |
||||
|
echo "" >> $CURRENT_BINARY_DIR/BuildInfo.h.tmp |
||||
|
echo "#pragma once" >> $CURRENT_BINARY_DIR/BuildInfo.h.tmp |
||||
|
echo "" >> $CURRENT_BINARY_DIR/BuildInfo.h.tmp |
||||
|
echo "#define ETH_COMMIT_HASH $ETH_COMMIT_HASH" >> $CURRENT_BINARY_DIR/BuildInfo.h.tmp |
||||
|
echo "#define ETH_CLEAN_REPO $ETH_CLEAN_REPO" >> $CURRENT_BINARY_DIR/BuildInfo.h.tmp |
||||
|
echo "#define ETH_BUILD_TYPE $BUILD_TYPE" >> $CURRENT_BINARY_DIR/BuildInfo.h.tmp |
||||
|
echo "#define ETH_BUILD_PLATFORM $BUILD_PLATFORM" >> $CURRENT_BINARY_DIR/BuildInfo.h.tmp |
||||
|
fi |
||||
|
|
||||
|
if [[ -e "$CURRENT_BINARY_DIR/BuildInfo.h" ]] |
||||
|
then |
||||
|
DIFF=$(diff $CURRENT_BINARY_DIR/BuildInfo.h $CURRENT_BINARY_DIR/BuildInfo.h.tmp) |
||||
|
if [[ -z "$DIFF" ]] |
||||
|
then |
||||
|
rm $CURRENT_BINARY_DIR/BuildInfo.h.tmp |
||||
|
else |
||||
|
mv $CURRENT_BINARY_DIR/BuildInfo.h.tmp $CURRENT_BINARY_DIR/BuildInfo.h |
||||
|
fi |
||||
|
else |
||||
|
mv $CURRENT_BINARY_DIR/BuildInfo.h.tmp $CURRENT_BINARY_DIR/BuildInfo.h |
||||
|
fi |
@ -1,94 +0,0 @@ |
|||||
### UP FOR GRABS |
|
||||
|
|
||||
BUG: Nonce sometimes jumps one. |
|
||||
|
|
||||
Tests |
|
||||
- Use standard tests. |
|
||||
|
|
||||
Crypto stuff: |
|
||||
- kFromMessage |
|
||||
- Check all the tweak instructions. |
|
||||
|
|
||||
Network: |
|
||||
- *** Exponential backoff on bad connection. |
|
||||
- *** Handle exception when no network. |
|
||||
- *** Only download blocks from one peer at once. |
|
||||
- Download in parallel, but stripe. |
|
||||
- NotInChain will be very bad for new peers - it'll run through until the genesis. |
|
||||
- Check how many it has first. |
|
||||
- Crypto on network - use id as public key? |
|
||||
- Make work with IPv6 |
|
||||
- Peers rated. |
|
||||
- Useful/useless - new blocks/transactions or useful peers? |
|
||||
- Solid communications? |
|
||||
- Strategy for peer suggestion? |
|
||||
- Ignore transactions with future nonces until address's nonce changes. |
|
||||
|
|
||||
Cleanups & caching |
|
||||
- All caches should flush unused data (I'm looking at you, BlockChain) to avoid memory overload. |
|
||||
- State DB should keep only last few N blocks worth of nodes (except for restore points - configurable, defaults to every 30000th block - all blocks that are restore points should be stored so their stateRoots are known good). |
|
||||
|
|
||||
THREAD-SAFETY |
|
||||
- BlockChain |
|
||||
- TransactionQueue |
|
||||
- State |
|
||||
|
|
||||
General: |
|
||||
- Better logging. |
|
||||
- Colours. |
|
||||
- Move over to new system. |
|
||||
- Remove block chain on protocol change (i.e. store protocol with block chain). |
|
||||
|
|
||||
Robustness |
|
||||
- Remove aborts |
|
||||
- Recover from all exceptions. |
|
||||
- Especially RLP & other I/O. |
|
||||
- RLP should never assert; only throw. |
|
||||
- Store version alongside BC DB. |
|
||||
- Better handling of corrupt blocks. |
|
||||
- Kill DB & restart. |
|
||||
- Avoid transactions with future invalid nonces until additional transactions are processed. |
|
||||
|
|
||||
GUI |
|
||||
- Make address/block chain list model-based, JIT populated. |
|
||||
- Make everything else model-based |
|
||||
- Qt/QML class. |
|
||||
- Turn on/off debug channels. |
|
||||
|
|
||||
|
|
||||
### Marko |
|
||||
|
|
||||
Ubuntu builds |
|
||||
- Raring (branch, local, x64 only :-( ) |
|
||||
- Quantal (branch) (Launchpad) |
|
||||
- Saucy (master) (Launchpad) |
|
||||
|
|
||||
### Alex |
|
||||
|
|
||||
Mac build. |
|
||||
Mac build instructions. |
|
||||
|
|
||||
### Eric |
|
||||
|
|
||||
Windows XC build. |
|
||||
Windows XC build instructions. |
|
||||
|
|
||||
### Tim/Harv |
|
||||
|
|
||||
Windows MSVC build. |
|
||||
Windows MSVC build instructions. |
|
||||
|
|
||||
LATER: |
|
||||
|
|
||||
Trie on DB. |
|
||||
- Move the restore point stuff into block restore points |
|
||||
- i.e. keep all nodes from last 127 blocks with counter, at 128, kill but keep every (60*24*7)th or so i.e. one per week as a restore point. |
|
||||
- maybe allow this to be configured. |
|
||||
|
|
||||
|
|
||||
### TIM |
|
||||
|
|
||||
Stateful Miner class. |
|
||||
|
|
||||
Better Mod-Exp. |
|
||||
|
|
@ -1,6 +0,0 @@ |
|||||
cpp-ethereum for Debian |
|
||||
----------------------- |
|
||||
|
|
||||
<possible notes regarding this package - if none, delete this file> |
|
||||
|
|
||||
-- Gav <Gav Wood <i@gavwood.com>> Mon, 03 Feb 2014 14:50:20 +0000 |
|
@ -1,9 +0,0 @@ |
|||||
cpp-ethereum for Debian |
|
||||
----------------------- |
|
||||
|
|
||||
<this file describes information about the source package, see Debian policy |
|
||||
manual section 4.14. You WILL either need to modify or delete this file> |
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
@ -1,23 +0,0 @@ |
|||||
cpp-ethereum (0.1.0-1) saucy; urgency=low |
|
||||
|
|
||||
* Various improvements moving towards PoC-2. |
|
||||
|
|
||||
-- Ethereum (Ethereum Project) <ethereum@gavwood.com> Fri, 07 Feb 2014 00:48:26 +0000 |
|
||||
|
|
||||
cpp-ethereum (0.1-3) saucy; urgency=low |
|
||||
|
|
||||
* Packaging stuff. |
|
||||
|
|
||||
-- Ethereum (Ethereum Project) <ethereum@gavwood.com> Thu, 06 Feb 2014 14:13:00 +0000 |
|
||||
|
|
||||
cpp-ethereum (0.1-2) saucy; urgency=low |
|
||||
|
|
||||
* Fix Qt dep. |
|
||||
|
|
||||
-- Gav Wood <ethereum@gavwood.com> Mon, 06 Feb 2014 11:35:20 +0000 |
|
||||
|
|
||||
cpp-ethereum (0.1-1) saucy; urgency=low |
|
||||
|
|
||||
* Initial release. |
|
||||
|
|
||||
-- Gav Wood <ethereum@gavwood.com> Mon, 03 Feb 2014 14:50:20 +0000 |
|
@ -1 +0,0 @@ |
|||||
8 |
|
@ -1,29 +0,0 @@ |
|||||
Source: cpp-ethereum |
|
||||
Section: science |
|
||||
Priority: extra |
|
||||
Maintainer: Ethereum (Ethereum Project) <ethereum@gavwood.com> |
|
||||
Build-Depends: debhelper (>= 8.0.0), cmake, libgmp-dev, libcryptoppeth-dev, libboost-filesystem1.53-dev, libboost-mpi1.53-dev, libboost1.53-dev, libleveldb-dev, libminiupnpc-dev, qtbase5-dev, qt5-default |
|
||||
Standards-Version: 3.9.4 |
|
||||
Homepage: http://ethereum.org |
|
||||
#Vcs-Git: git://git.debian.org/collab-maint/cpp-ethereum.git |
|
||||
#Vcs-Browser: http://git.debian.org/?p=collab-maint/cpp-ethereum.git;a=summary |
|
||||
|
|
||||
Package: libethereum |
|
||||
Section: libdevel |
|
||||
Architecture: any |
|
||||
Depends: ${shlibs:Depends}, ${misc:Depends}, libgmp, libcryptoppeth, libboost-filesystem1.53.0, libboost-mpi1.53.0, libleveldb, libminiupnpc, secp256k1eth |
|
||||
Description: The future of computational social contracts. |
|
||||
A long description of libethereum. |
|
||||
|
|
||||
Package: libethereum-dev |
|
||||
Section: libdevel |
|
||||
Architecture: any |
|
||||
Depends: libethereum (= ${binary:Version}), ${shlibs:Depends}, ${misc:Depends}, libgmp, libcryptoppeth, libboost-filesystem1.53.0, libboost-mpi1.53.0, libleveldb, libminiupnpc |
|
||||
Description: The future of computational social contracts. Dev Files. |
|
||||
A long description of libethereum. Dev Files. |
|
||||
|
|
||||
Package: alethzero |
|
||||
Architecture: any |
|
||||
Depends: ${shlibs:Depends}, ${misc:Depends}, libethereum (= ${binary:Version}), qtbase5 |
|
||||
Description: The Qt-based Ethereum GUI. |
|
||||
A long description of alethzero. |
|
@ -1,24 +0,0 @@ |
|||||
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ |
|
||||
Upstream-Name: cpp-ethereum |
|
||||
Source: github.com/ethereum/cpp-ethereum |
|
||||
|
|
||||
Files: * |
|
||||
Copyright: 2014 Gav Wood <i@gavwood.com> |
|
||||
License: MIT |
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy |
|
||||
of this software and associated documentation files (the "Software"), to deal |
|
||||
in the Software without restriction, including without limitation the rights |
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|
||||
copies of the Software, and to permit persons to whom the Software is |
|
||||
furnished to do so, subject to the following conditions: |
|
||||
|
|
||||
The above copyright notice and this permission notice shall be included in |
|
||||
all copies or substantial portions of the Software. |
|
||||
|
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
|
||||
THE SOFTWARE. |
|
@ -1,3 +0,0 @@ |
|||||
CodingStandards.txt |
|
||||
README.md |
|
||||
TODO |
|
@ -1,36 +0,0 @@ |
|||||
#!/usr/bin/make -f |
|
||||
# -*- makefile -*- |
|
||||
# Sample debian/rules that uses debhelper. |
|
||||
# |
|
||||
# This file was originally written by Joey Hess and Craig Small. |
|
||||
# As a special exception, when this file is copied by dh-make into a |
|
||||
# dh-make output file, you may use that output file without restriction. |
|
||||
# This special exception was added by Craig Small in version 0.37 of dh-make. |
|
||||
# |
|
||||
# Modified to make a template file for a multi-binary package with separated |
|
||||
# build-arch and build-indep targets by Bill Allombert 2001 |
|
||||
|
|
||||
# Uncomment this to turn on verbose mode. |
|
||||
#export DH_VERBOSE=1 |
|
||||
|
|
||||
# This has to be exported to make some magic below work. |
|
||||
export DH_OPTIONS |
|
||||
|
|
||||
|
|
||||
%: |
|
||||
dh $@ |
|
||||
|
|
||||
override_dh_auto_configure: |
|
||||
cmake -DCMAKE_INSTALL_PREFIX=$(CURDIR)/debian/libethereum-dev/usr . |
|
||||
|
|
||||
override_dh_auto_build: |
|
||||
$(MAKE) -j8 |
|
||||
|
|
||||
override_dh_auto_install: |
|
||||
$(MAKE) install |
|
||||
# Move the libs over to the non-dev package. |
|
||||
mkdir -p $(CURDIR)/debian/libethereum/usr |
|
||||
mv $(CURDIR)/debian/libethereum-dev/usr/lib $(CURDIR)/debian/libethereum/usr/lib |
|
||||
mkdir -p $(CURDIR)/debian/alephzero/usr/bin |
|
||||
mv $(CURDIR)/debian/libethereum-dev/usr/bin/alethzero $(CURDIR)/debian/alethzero/usr/lib |
|
||||
|
|
@ -0,0 +1,96 @@ |
|||||
|
/*
|
||||
|
This file is part of cpp-ethereum. |
||||
|
|
||||
|
cpp-ethereum is free software: you can redistribute it and/or modify |
||||
|
it under the terms of the GNU General Public License as published by |
||||
|
the Free Software Foundation, either version 3 of the License, or |
||||
|
(at your option) any later version. |
||||
|
|
||||
|
cpp-ethereum is distributed in the hope that it will be useful, |
||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
GNU General Public License for more details. |
||||
|
|
||||
|
You should have received a copy of the GNU General Public License |
||||
|
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
*/ |
||||
|
/** @file Common.cpp
|
||||
|
* @author Gav Wood <i@gavwood.com> |
||||
|
* @date 2014 |
||||
|
*/ |
||||
|
|
||||
|
#include "CommonData.h" |
||||
|
|
||||
|
#include <random> |
||||
|
#include "Exceptions.h" |
||||
|
using namespace std; |
||||
|
using namespace eth; |
||||
|
|
||||
|
std::string eth::escaped(std::string const& _s, bool _all) |
||||
|
{ |
||||
|
std::string ret; |
||||
|
ret.reserve(_s.size()); |
||||
|
ret.push_back('"'); |
||||
|
for (auto i: _s) |
||||
|
if (i == '"' && !_all) |
||||
|
ret += "\\\""; |
||||
|
else if (i == '\\' && !_all) |
||||
|
ret += "\\\\"; |
||||
|
else if (i < ' ' || i > 127 || _all) |
||||
|
{ |
||||
|
ret += "\\x"; |
||||
|
ret.push_back("0123456789abcdef"[(uint8_t)i / 16]); |
||||
|
ret.push_back("0123456789abcdef"[(uint8_t)i % 16]); |
||||
|
} |
||||
|
else |
||||
|
ret.push_back(i); |
||||
|
ret.push_back('"'); |
||||
|
return ret; |
||||
|
} |
||||
|
|
||||
|
std::string eth::randomWord() |
||||
|
{ |
||||
|
static std::mt19937_64 s_eng(0); |
||||
|
std::string ret(std::uniform_int_distribution<int>(4, 10)(s_eng), ' '); |
||||
|
char const n[] = "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM1234567890"; |
||||
|
std::uniform_int_distribution<int> d(0, sizeof(n) - 2); |
||||
|
for (char& c: ret) |
||||
|
c = n[d(s_eng)]; |
||||
|
return ret; |
||||
|
} |
||||
|
|
||||
|
int eth::fromHex(char _i) |
||||
|
{ |
||||
|
if (_i >= '0' && _i <= '9') |
||||
|
return _i - '0'; |
||||
|
if (_i >= 'a' && _i <= 'f') |
||||
|
return _i - 'a' + 10; |
||||
|
if (_i >= 'A' && _i <= 'F') |
||||
|
return _i - 'A' + 10; |
||||
|
throw BadHexCharacter(); |
||||
|
} |
||||
|
|
||||
|
bytes eth::fromHex(std::string const& _s) |
||||
|
{ |
||||
|
assert(_s.size() % 2 == 0); |
||||
|
if (_s.size() < 2) |
||||
|
return bytes(); |
||||
|
uint s = (_s[0] == '0' && _s[1] == 'x') ? 2 : 0; |
||||
|
std::vector<uint8_t> ret; |
||||
|
ret.reserve((_s.size() - s) / 2); |
||||
|
for (uint i = s; i < _s.size(); i += 2) |
||||
|
ret.push_back((byte)(fromHex(_s[i]) * 16 + fromHex(_s[i + 1]))); |
||||
|
return ret; |
||||
|
} |
||||
|
|
||||
|
bytes eth::asNibbles(std::string const& _s) |
||||
|
{ |
||||
|
std::vector<uint8_t> ret; |
||||
|
ret.reserve(_s.size() * 2); |
||||
|
for (auto i: _s) |
||||
|
{ |
||||
|
ret.push_back(i / 16); |
||||
|
ret.push_back(i % 16); |
||||
|
} |
||||
|
return ret; |
||||
|
} |
@ -0,0 +1,186 @@ |
|||||
|
/*
|
||||
|
This file is part of cpp-ethereum. |
||||
|
|
||||
|
cpp-ethereum is free software: you can redistribute it and/or modify |
||||
|
it under the terms of the GNU General Public License as published by |
||||
|
the Free Software Foundation, either version 3 of the License, or |
||||
|
(at your option) any later version. |
||||
|
|
||||
|
cpp-ethereum is distributed in the hope that it will be useful, |
||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
GNU General Public License for more details. |
||||
|
|
||||
|
You should have received a copy of the GNU General Public License |
||||
|
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
*/ |
||||
|
/** @file Common.h
|
||||
|
* @author Gav Wood <i@gavwood.com> |
||||
|
* @date 2014 |
||||
|
* |
||||
|
* Shared algorithms and data types. |
||||
|
*/ |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <vector> |
||||
|
#include <algorithm> |
||||
|
#include <type_traits> |
||||
|
#include <cstring> |
||||
|
#include <string> |
||||
|
#include "Common.h" |
||||
|
|
||||
|
namespace eth |
||||
|
{ |
||||
|
|
||||
|
// String conversion functions, mainly to/from hex/nibble/byte representations.
|
||||
|
|
||||
|
/// Convert a series of bytes to the corresponding string of hex duplets.
|
||||
|
/// @param _w specifies the width of each of the elements. Defaults to two - enough to represent a byte.
|
||||
|
/// @example toHex("A\x69") == "4169"
|
||||
|
template <class _T> |
||||
|
std::string toHex(_T const& _data, int _w = 2) |
||||
|
{ |
||||
|
std::ostringstream ret; |
||||
|
for (auto i: _data) |
||||
|
ret << std::hex << std::setfill('0') << std::setw(_w) << (int)(typename std::make_unsigned<decltype(i)>::type)i; |
||||
|
return ret.str(); |
||||
|
} |
||||
|
|
||||
|
/// Converts a (printable) ASCII hex character into the correspnding integer value.
|
||||
|
/// @example fromHex('A') == 10 && fromHex('f') == 15 && fromHex('5') == 5
|
||||
|
int fromHex(char _i); |
||||
|
|
||||
|
/// Converts a (printable) ASCII hex string into the corresponding byte stream.
|
||||
|
/// @example fromHex("41626261") == asBytes("Abba")
|
||||
|
bytes fromHex(std::string const& _s); |
||||
|
|
||||
|
/// Converts byte array to a string containing the same (binary) data. Unless
|
||||
|
/// the byte array happens to contain ASCII data, this won't be printable.
|
||||
|
inline std::string asString(bytes const& _b) |
||||
|
{ |
||||
|
return std::string((char const*)_b.data(), (char const*)(_b.data() + _b.size())); |
||||
|
} |
||||
|
|
||||
|
/// Converts a string to a byte array containing the string's (byte) data.
|
||||
|
inline bytes asBytes(std::string const& _b) |
||||
|
{ |
||||
|
return bytes((byte const*)_b.data(), (byte const*)(_b.data() + _b.size())); |
||||
|
} |
||||
|
|
||||
|
/// Converts a string into the big-endian base-16 stream of integers (NOT ASCII).
|
||||
|
/// @example asNibbles("A")[0] == 4 && asNibbles("A")[1] == 1
|
||||
|
bytes asNibbles(std::string const& _s); |
||||
|
|
||||
|
|
||||
|
// Big-endian to/from host endian conversion functions.
|
||||
|
|
||||
|
/// Converts a templated integer value to the big-endian byte-stream represented on a templated collection.
|
||||
|
/// The size of the collection object will be unchanged. If it is too small, it will not represent the
|
||||
|
/// value properly, if too big then the additional elements will be zeroed out.
|
||||
|
/// @a _Out will typically be either std::string or bytes.
|
||||
|
/// @a _T will typically by uint, u160, u256 or bigint.
|
||||
|
template <class _T, class _Out> |
||||
|
inline void toBigEndian(_T _val, _Out& o_out) |
||||
|
{ |
||||
|
for (auto i = o_out.size(); i-- != 0; _val >>= 8) |
||||
|
o_out[i] = (typename _Out::value_type)(uint8_t)_val; |
||||
|
} |
||||
|
|
||||
|
/// Converts a big-endian byte-stream represented on a templated collection to a templated integer value.
|
||||
|
/// @a _In will typically be either std::string or bytes.
|
||||
|
/// @a _T will typically by uint, u160, u256 or bigint.
|
||||
|
template <class _T, class _In> |
||||
|
inline _T fromBigEndian(_In const& _bytes) |
||||
|
{ |
||||
|
_T ret = 0; |
||||
|
for (auto i: _bytes) |
||||
|
ret = (ret << 8) | (byte)(typename std::make_unsigned<typename _In::value_type>::type)i; |
||||
|
return ret; |
||||
|
} |
||||
|
|
||||
|
/// Convenience functions for toBigEndian
|
||||
|
inline std::string toBigEndianString(u256 _val) { std::string ret(32, '\0'); toBigEndian(_val, ret); return ret; } |
||||
|
inline std::string toBigEndianString(u160 _val) { std::string ret(20, '\0'); toBigEndian(_val, ret); return ret; } |
||||
|
inline bytes toBigEndian(u256 _val) { bytes ret(32); toBigEndian(_val, ret); return ret; } |
||||
|
inline bytes toBigEndian(u160 _val) { bytes ret(20); toBigEndian(_val, ret); return ret; } |
||||
|
|
||||
|
/// Convenience function for toBigEndian.
|
||||
|
/// @returns a string just big enough to represent @a _val.
|
||||
|
template <class _T> |
||||
|
inline std::string toCompactBigEndianString(_T _val) |
||||
|
{ |
||||
|
int i = 0; |
||||
|
for (_T v = _val; v; ++i, v >>= 8) {} |
||||
|
std::string ret(i, '\0'); |
||||
|
toBigEndian(_val, ret); |
||||
|
return ret; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
// Algorithms for string and string-like collections.
|
||||
|
|
||||
|
/// Escapes a string into the C-string representation.
|
||||
|
/// @p _all if true will escape all characters, not just the unprintable ones.
|
||||
|
std::string escaped(std::string const& _s, bool _all = true); |
||||
|
|
||||
|
/// Determines the length of the common prefix of the two collections given.
|
||||
|
/// @returns the number of elements both @a _t and @a _u share, in order, at the beginning.
|
||||
|
/// @example commonPrefix("Hello world!", "Hello, world!") == 5
|
||||
|
template <class _T, class _U> |
||||
|
uint commonPrefix(_T const& _t, _U const& _u) |
||||
|
{ |
||||
|
uint s = std::min<uint>(_t.size(), _u.size()); |
||||
|
for (uint i = 0;; ++i) |
||||
|
if (i == s || _t[i] != _u[i]) |
||||
|
return i; |
||||
|
return s; |
||||
|
} |
||||
|
|
||||
|
/// Creates a random, printable, word.
|
||||
|
std::string randomWord(); |
||||
|
|
||||
|
|
||||
|
// General datatype convenience functions.
|
||||
|
|
||||
|
/// Trims a given number of elements from the front of a collection.
|
||||
|
/// Only works for POD element types.
|
||||
|
template <class _T> |
||||
|
void trimFront(_T& _t, uint _elements) |
||||
|
{ |
||||
|
static_assert(std::is_pod<typename _T::value_type>::value, ""); |
||||
|
memmove(_t.data(), _t.data() + _elements, (_t.size() - _elements) * sizeof(_t[0])); |
||||
|
_t.resize(_t.size() - _elements); |
||||
|
} |
||||
|
|
||||
|
/// Pushes an element on to the front of a collection.
|
||||
|
/// Only works for POD element types.
|
||||
|
template <class _T, class _U> |
||||
|
void pushFront(_T& _t, _U _e) |
||||
|
{ |
||||
|
static_assert(std::is_pod<typename _T::value_type>::value, ""); |
||||
|
_t.push_back(_e); |
||||
|
memmove(_t.data() + 1, _t.data(), (_t.size() - 1) * sizeof(_e)); |
||||
|
_t[0] = _e; |
||||
|
} |
||||
|
|
||||
|
/// Concatenate two vectors of elements. _T must be POD.
|
||||
|
template <class _T> |
||||
|
inline std::vector<_T>& operator+=(std::vector<typename std::enable_if<std::is_pod<_T>::value, _T>::type>& _a, std::vector<_T> const& _b) |
||||
|
{ |
||||
|
auto s = _a.size(); |
||||
|
_a.resize(_a.size() + _b.size()); |
||||
|
memcpy(_a.data() + s, _b.data(), _b.size() * sizeof(_T)); |
||||
|
return _a; |
||||
|
|
||||
|
} |
||||
|
|
||||
|
/// Concatenate two vectors of elements. _T must be POD.
|
||||
|
template <class _T> |
||||
|
inline std::vector<_T> operator+(std::vector<typename std::enable_if<std::is_pod<_T>::value, _T>::type> const& _a, std::vector<_T> const& _b) |
||||
|
{ |
||||
|
std::vector<_T> ret(_a); |
||||
|
return ret += _b; |
||||
|
} |
||||
|
|
||||
|
} |
@ -0,0 +1,200 @@ |
|||||
|
/*
|
||||
|
This file is part of cpp-ethereum. |
||||
|
|
||||
|
cpp-ethereum is free software: you can redistribute it and/or modify |
||||
|
it under the terms of the GNU General Public License as published by |
||||
|
the Free Software Foundation, either version 3 of the License, or |
||||
|
(at your option) any later version. |
||||
|
|
||||
|
cpp-ethereum is distributed in the hope that it will be useful, |
||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
GNU General Public License for more details. |
||||
|
|
||||
|
You should have received a copy of the GNU General Public License |
||||
|
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
*/ |
||||
|
/** @file CommonEth.cpp
|
||||
|
* @author Gav Wood <i@gavwood.com> |
||||
|
* @date 2014 |
||||
|
*/ |
||||
|
|
||||
|
#include "CommonEth.h" |
||||
|
|
||||
|
#if WIN32 |
||||
|
#pragma warning(push) |
||||
|
#pragma warning(disable:4244) |
||||
|
#else |
||||
|
#pragma GCC diagnostic ignored "-Wunused-function" |
||||
|
#endif |
||||
|
#include <secp256k1.h> |
||||
|
#include <sha3.h> |
||||
|
#if WIN32 |
||||
|
#pragma warning(pop) |
||||
|
#else |
||||
|
#endif |
||||
|
#include "Exceptions.h" |
||||
|
using namespace std; |
||||
|
using namespace eth; |
||||
|
|
||||
|
//#define ETH_ADDRESS_DEBUG 1
|
||||
|
|
||||
|
static const vector<pair<u256, string>> g_units = |
||||
|
{ |
||||
|
{((((u256(1000000000) * 1000000000) * 1000000000) * 1000000000) * 1000000000) * 1000000000, "Uether"}, |
||||
|
{((((u256(1000000000) * 1000000000) * 1000000000) * 1000000000) * 1000000000) * 1000000, "Vether"}, |
||||
|
{((((u256(1000000000) * 1000000000) * 1000000000) * 1000000000) * 1000000000) * 1000, "Dether"}, |
||||
|
{(((u256(1000000000) * 1000000000) * 1000000000) * 1000000000) * 1000000000, "Nether"}, |
||||
|
{(((u256(1000000000) * 1000000000) * 1000000000) * 1000000000) * 1000000, "Yether"}, |
||||
|
{(((u256(1000000000) * 1000000000) * 1000000000) * 1000000000) * 1000, "Zether"}, |
||||
|
{((u256(1000000000) * 1000000000) * 1000000000) * 1000000000, "Eether"}, |
||||
|
{((u256(1000000000) * 1000000000) * 1000000000) * 1000000, "Pether"}, |
||||
|
{((u256(1000000000) * 1000000000) * 1000000000) * 1000, "Tether"}, |
||||
|
{(u256(1000000000) * 1000000000) * 1000000000, "Gether"}, |
||||
|
{(u256(1000000000) * 1000000000) * 1000000, "Mether"}, |
||||
|
{(u256(1000000000) * 1000000000) * 1000, "Kether"}, |
||||
|
{u256(1000000000) * 1000000000, "ether"}, |
||||
|
{u256(1000000000) * 1000000, "finney"}, |
||||
|
{u256(1000000000) * 1000, "szabo"}, |
||||
|
{u256(1000000000), "Gwei"}, |
||||
|
{u256(1000000), "Mwei"}, |
||||
|
{u256(1000), "Kwei"}, |
||||
|
{u256(1), "wei"} |
||||
|
}; |
||||
|
|
||||
|
vector<pair<u256, string>> const& eth::units() |
||||
|
{ |
||||
|
return g_units; |
||||
|
} |
||||
|
|
||||
|
std::string eth::formatBalance(u256 _b) |
||||
|
{ |
||||
|
ostringstream ret; |
||||
|
if (_b > g_units[0].first * 10000) |
||||
|
{ |
||||
|
ret << (_b / g_units[0].first) << " " << g_units[0].second; |
||||
|
return ret.str(); |
||||
|
} |
||||
|
ret << setprecision(5); |
||||
|
for (auto const& i: g_units) |
||||
|
if (i.first != 1 && _b >= i.first * 100) |
||||
|
{ |
||||
|
ret << (double(_b / (i.first / 1000)) / 1000.0) << " " << i.second; |
||||
|
return ret.str(); |
||||
|
} |
||||
|
ret << _b << " wei"; |
||||
|
return ret.str(); |
||||
|
} |
||||
|
|
||||
|
Address eth::toAddress(Secret _private) |
||||
|
{ |
||||
|
secp256k1_start(); |
||||
|
|
||||
|
byte pubkey[65]; |
||||
|
int pubkeylen = 65; |
||||
|
int ok = secp256k1_ecdsa_seckey_verify(_private.data()); |
||||
|
if (!ok) |
||||
|
return Address(); |
||||
|
ok = secp256k1_ecdsa_pubkey_create(pubkey, &pubkeylen, _private.data(), 0); |
||||
|
assert(pubkeylen == 65); |
||||
|
if (!ok) |
||||
|
return Address(); |
||||
|
ok = secp256k1_ecdsa_pubkey_verify(pubkey, 65); |
||||
|
if (!ok) |
||||
|
return Address(); |
||||
|
auto ret = right160(eth::sha3(bytesConstRef(&(pubkey[1]), 64))); |
||||
|
#if ETH_ADDRESS_DEBUG |
||||
|
cout << "---- ADDRESS -------------------------------" << endl; |
||||
|
cout << "SEC: " << _private << endl; |
||||
|
cout << "PUB: " << toHex(bytesConstRef(&(pubkey[1]), 64)) << endl; |
||||
|
cout << "ADR: " << ret << endl; |
||||
|
#endif |
||||
|
return ret; |
||||
|
} |
||||
|
|
||||
|
KeyPair KeyPair::create() |
||||
|
{ |
||||
|
secp256k1_start(); |
||||
|
static std::mt19937_64 s_eng(time(0)); |
||||
|
std::uniform_int_distribution<uint16_t> d(0, 255); |
||||
|
|
||||
|
for (int i = 0; i < 100; ++i) |
||||
|
{ |
||||
|
h256 sec; |
||||
|
for (unsigned i = 0; i < 32; ++i) |
||||
|
sec[i] = (byte)d(s_eng); |
||||
|
|
||||
|
KeyPair ret(sec); |
||||
|
if (ret.address()) |
||||
|
return ret; |
||||
|
} |
||||
|
return KeyPair(); |
||||
|
} |
||||
|
|
||||
|
KeyPair::KeyPair(h256 _sec): |
||||
|
m_secret(_sec) |
||||
|
{ |
||||
|
int ok = secp256k1_ecdsa_seckey_verify(m_secret.data()); |
||||
|
if (!ok) |
||||
|
return; |
||||
|
|
||||
|
byte pubkey[65]; |
||||
|
int pubkeylen = 65; |
||||
|
ok = secp256k1_ecdsa_pubkey_create(pubkey, &pubkeylen, m_secret.data(), 0); |
||||
|
if (!ok || pubkeylen != 65) |
||||
|
return; |
||||
|
|
||||
|
ok = secp256k1_ecdsa_pubkey_verify(pubkey, 65); |
||||
|
if (!ok) |
||||
|
return; |
||||
|
|
||||
|
m_secret = m_secret; |
||||
|
memcpy(m_public.data(), &(pubkey[1]), 64); |
||||
|
m_address = right160(eth::sha3(bytesConstRef(&(pubkey[1]), 64))); |
||||
|
|
||||
|
#if ETH_ADDRESS_DEBUG |
||||
|
cout << "---- ADDRESS -------------------------------" << endl; |
||||
|
cout << "SEC: " << m_secret << endl; |
||||
|
cout << "PUB: " << m_public << endl; |
||||
|
cout << "ADR: " << m_address << endl; |
||||
|
#endif |
||||
|
} |
||||
|
|
||||
|
std::string eth::sha3(std::string const& _input, bool _hex) |
||||
|
{ |
||||
|
if (!_hex) |
||||
|
{ |
||||
|
string ret(32, '\0'); |
||||
|
sha3(bytesConstRef((byte const*)_input.data(), _input.size()), bytesRef((byte*)ret.data(), 32)); |
||||
|
return ret; |
||||
|
} |
||||
|
|
||||
|
uint8_t buf[32]; |
||||
|
sha3(bytesConstRef((byte const*)_input.data(), _input.size()), bytesRef((byte*)&(buf[0]), 32)); |
||||
|
std::string ret(64, '\0'); |
||||
|
for (unsigned int i = 0; i < 32; i++) |
||||
|
sprintf((char*)(ret.data())+i*2, "%02x", buf[i]); |
||||
|
return ret; |
||||
|
} |
||||
|
|
||||
|
void eth::sha3(bytesConstRef _input, bytesRef _output) |
||||
|
{ |
||||
|
CryptoPP::SHA3_256 ctx; |
||||
|
ctx.Update((byte*)_input.data(), _input.size()); |
||||
|
assert(_output.size() >= 32); |
||||
|
ctx.Final(_output.data()); |
||||
|
} |
||||
|
|
||||
|
bytes eth::sha3Bytes(bytesConstRef _input) |
||||
|
{ |
||||
|
bytes ret(32); |
||||
|
sha3(_input, &ret); |
||||
|
return ret; |
||||
|
} |
||||
|
|
||||
|
h256 eth::sha3(bytesConstRef _input) |
||||
|
{ |
||||
|
h256 ret; |
||||
|
sha3(_input, bytesRef(&ret[0], 32)); |
||||
|
return ret; |
||||
|
} |
@ -0,0 +1,137 @@ |
|||||
|
/*
|
||||
|
This file is part of cpp-ethereum. |
||||
|
|
||||
|
cpp-ethereum is free software: you can redistribute it and/or modify |
||||
|
it under the terms of the GNU General Public License as published by |
||||
|
the Free Software Foundation, either version 3 of the License, or |
||||
|
(at your option) any later version. |
||||
|
|
||||
|
cpp-ethereum is distributed in the hope that it will be useful, |
||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
GNU General Public License for more details. |
||||
|
|
||||
|
You should have received a copy of the GNU General Public License |
||||
|
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
*/ |
||||
|
/** @file CommonEth.h
|
||||
|
* @author Gav Wood <i@gavwood.com> |
||||
|
* @date 2014 |
||||
|
* |
||||
|
* Ethereum-specific data structures & algorithms. |
||||
|
*/ |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include "Common.h" |
||||
|
#include "FixedHash.h" |
||||
|
|
||||
|
namespace eth |
||||
|
{ |
||||
|
|
||||
|
/// A secret key: 32 bytes.
|
||||
|
/// @NOTE This is not endian-specific; it's just a bunch of bytes.
|
||||
|
using Secret = h256; |
||||
|
|
||||
|
/// A public key: 64 bytes.
|
||||
|
/// @NOTE This is not endian-specific; it's just a bunch of bytes.
|
||||
|
using Public = h512; |
||||
|
|
||||
|
/// An Ethereum address: 20 bytes.
|
||||
|
/// @NOTE This is not endian-specific; it's just a bunch of bytes.
|
||||
|
using Address = h160; |
||||
|
|
||||
|
/// A vector of Ethereum addresses.
|
||||
|
using Addresses = h160s; |
||||
|
|
||||
|
/// User-friendly string representation of the amount _b in wei.
|
||||
|
std::string formatBalance(u256 _b); |
||||
|
|
||||
|
/// Get information concerning the currency denominations.
|
||||
|
std::vector<std::pair<u256, std::string>> const& units(); |
||||
|
|
||||
|
// The various denominations; here for ease of use where needed within code.
|
||||
|
static const u256 Uether = ((((u256(1000000000) * 1000000000) * 1000000000) * 1000000000) * 1000000000) * 1000000000; |
||||
|
static const u256 Vether = ((((u256(1000000000) * 1000000000) * 1000000000) * 1000000000) * 1000000000) * 1000000; |
||||
|
static const u256 Dether = ((((u256(1000000000) * 1000000000) * 1000000000) * 1000000000) * 1000000000) * 1000; |
||||
|
static const u256 Nether = (((u256(1000000000) * 1000000000) * 1000000000) * 1000000000) * 1000000000; |
||||
|
static const u256 Yether = (((u256(1000000000) * 1000000000) * 1000000000) * 1000000000) * 1000000; |
||||
|
static const u256 Zether = (((u256(1000000000) * 1000000000) * 1000000000) * 1000000000) * 1000; |
||||
|
static const u256 Eether = ((u256(1000000000) * 1000000000) * 1000000000) * 1000000000; |
||||
|
static const u256 Pether = ((u256(1000000000) * 1000000000) * 1000000000) * 1000000; |
||||
|
static const u256 Tether = ((u256(1000000000) * 1000000000) * 1000000000) * 1000; |
||||
|
static const u256 Gether = (u256(1000000000) * 1000000000) * 1000000000; |
||||
|
static const u256 Mether = (u256(1000000000) * 1000000000) * 1000000; |
||||
|
static const u256 Kether = (u256(1000000000) * 1000000000) * 1000; |
||||
|
static const u256 ether = u256(1000000000) * 1000000000; |
||||
|
static const u256 finney = u256(1000000000) * 1000000; |
||||
|
static const u256 szabo = u256(1000000000) * 1000; |
||||
|
static const u256 Gwei = u256(1000000000); |
||||
|
static const u256 Mwei = u256(1000000); |
||||
|
static const u256 Kwei = u256(1000); |
||||
|
static const u256 wei = u256(1); |
||||
|
|
||||
|
/// Convert a private key into the public key equivalent.
|
||||
|
/// @returns 0 if it's not a valid private key.
|
||||
|
Address toAddress(h256 _private); |
||||
|
|
||||
|
/// Simple class that represents a "key pair".
|
||||
|
/// All of the data of the class can be regenerated from the secret key (m_secret) alone.
|
||||
|
/// Actually stores a tuplet of secret, public and address (the right 160-bits of the public).
|
||||
|
class KeyPair |
||||
|
{ |
||||
|
public: |
||||
|
/// Null constructor.
|
||||
|
KeyPair() {} |
||||
|
|
||||
|
/// Normal constructor - populates object from the given secret key.
|
||||
|
KeyPair(Secret _k); |
||||
|
|
||||
|
/// Create a new, randomly generated object.
|
||||
|
static KeyPair create(); |
||||
|
|
||||
|
/// Retrieve the secret key.
|
||||
|
Secret const& secret() const { return m_secret; } |
||||
|
/// Retrieve the secret key.
|
||||
|
Secret const& sec() const { return m_secret; } |
||||
|
|
||||
|
/// Retrieve the public key.
|
||||
|
Public const& pub() const { return m_public; } |
||||
|
|
||||
|
/// Retrieve the associated address of the public key.
|
||||
|
Address const& address() const { return m_address; } |
||||
|
|
||||
|
private: |
||||
|
Secret m_secret; |
||||
|
Public m_public; |
||||
|
Address m_address; |
||||
|
}; |
||||
|
|
||||
|
|
||||
|
// SHA-3 convenience routines.
|
||||
|
|
||||
|
/// Calculate SHA3-256 hash of the given input and load it into the given output.
|
||||
|
void sha3(bytesConstRef _input, bytesRef _output); |
||||
|
|
||||
|
/// Calculate SHA3-256 hash of the given input, possibly interpreting it as nibbles, and return the hash as a string filled with binary data.
|
||||
|
std::string sha3(std::string const& _input, bool _isNibbles); |
||||
|
|
||||
|
/// Calculate SHA3-256 hash of the given input, returning as a byte array.
|
||||
|
bytes sha3Bytes(bytesConstRef _input); |
||||
|
|
||||
|
/// Calculate SHA3-256 hash of the given input (presented as a binary string), returning as a byte array.
|
||||
|
inline bytes sha3Bytes(std::string const& _input) { return sha3Bytes((std::string*)&_input); } |
||||
|
|
||||
|
/// Calculate SHA3-256 hash of the given input, returning as a byte array.
|
||||
|
inline bytes sha3Bytes(bytes const& _input) { return sha3Bytes((bytes*)&_input); } |
||||
|
|
||||
|
/// Calculate SHA3-256 hash of the given input, returning as a 256-bit hash.
|
||||
|
h256 sha3(bytesConstRef _input); |
||||
|
|
||||
|
/// Calculate SHA3-256 hash of the given input, returning as a 256-bit hash.
|
||||
|
inline h256 sha3(bytes const& _input) { return sha3(bytesConstRef((bytes*)&_input)); } |
||||
|
|
||||
|
/// Calculate SHA3-256 hash of the given input (presented as a binary-filled string), returning as a 256-bit hash.
|
||||
|
inline h256 sha3(std::string const& _input) { return sha3(bytesConstRef(_input)); } |
||||
|
|
||||
|
} |
@ -0,0 +1,48 @@ |
|||||
|
/*
|
||||
|
This file is part of cpp-ethereum. |
||||
|
|
||||
|
cpp-ethereum is free software: you can redistribute it and/or modify |
||||
|
it under the terms of the GNU General Public License as published by |
||||
|
the Free Software Foundation, either version 3 of the License, or |
||||
|
(at your option) any later version. |
||||
|
|
||||
|
cpp-ethereum is distributed in the hope that it will be useful, |
||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
GNU General Public License for more details. |
||||
|
|
||||
|
You should have received a copy of the GNU General Public License |
||||
|
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
*/ |
||||
|
/** @file CommonIO.cpp
|
||||
|
* @author Gav Wood <i@gavwood.com> |
||||
|
* @date 2014 |
||||
|
*/ |
||||
|
|
||||
|
#include "Common.h" |
||||
|
|
||||
|
#include <fstream> |
||||
|
#include "Exceptions.h" |
||||
|
using namespace std; |
||||
|
using namespace eth; |
||||
|
|
||||
|
bytes eth::contents(std::string const& _file) |
||||
|
{ |
||||
|
std::ifstream is(_file, std::ifstream::binary); |
||||
|
if (!is) |
||||
|
return bytes(); |
||||
|
// get length of file:
|
||||
|
is.seekg (0, is.end); |
||||
|
streamoff length = is.tellg(); |
||||
|
is.seekg (0, is.beg); |
||||
|
bytes ret(length); |
||||
|
is.read((char*)ret.data(), length); |
||||
|
is.close(); |
||||
|
return ret; |
||||
|
} |
||||
|
|
||||
|
void eth::writeFile(std::string const& _file, bytes const& _data) |
||||
|
{ |
||||
|
ofstream(_file, ios::trunc).write((char const*)_data.data(), _data.size()); |
||||
|
} |
||||
|
|
@ -0,0 +1,223 @@ |
|||||
|
/*
|
||||
|
This file is part of cpp-ethereum. |
||||
|
|
||||
|
cpp-ethereum is free software: you can redistribute it and/or modify |
||||
|
it under the terms of the GNU General Public License as published by |
||||
|
the Free Software Foundation, either version 3 of the License, or |
||||
|
(at your option) any later version. |
||||
|
|
||||
|
cpp-ethereum is distributed in the hope that it will be useful, |
||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
GNU General Public License for more details. |
||||
|
|
||||
|
You should have received a copy of the GNU General Public License |
||||
|
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
*/ |
||||
|
/** @file CommonIO.h
|
||||
|
* @author Gav Wood <i@gavwood.com> |
||||
|
* @date 2014 |
||||
|
* |
||||
|
* File & stream I/O routines. |
||||
|
*/ |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <map> |
||||
|
#include <set> |
||||
|
#include <unordered_map> |
||||
|
#include <unordered_set> |
||||
|
#include <array> |
||||
|
#include <list> |
||||
|
#include <memory> |
||||
|
#include <vector> |
||||
|
#include <array> |
||||
|
#include <sstream> |
||||
|
#include <string> |
||||
|
#include <iostream> |
||||
|
#include "Common.h" |
||||
|
|
||||
|
namespace eth |
||||
|
{ |
||||
|
|
||||
|
/// Retrieve and returns the contents of the given file. If the file doesn't exist or isn't readable, returns an empty bytes.
|
||||
|
bytes contents(std::string const& _file); |
||||
|
|
||||
|
/// Write the given binary data into the given file, replacing the file if it pre-exists.
|
||||
|
void writeFile(std::string const& _file, bytes const& _data); |
||||
|
|
||||
|
/// Converts arbitrary value to string representation using std::stringstream.
|
||||
|
template <class _T> |
||||
|
std::string toString(_T const& _t) |
||||
|
{ |
||||
|
std::ostringstream o; |
||||
|
o << _t; |
||||
|
return o.str(); |
||||
|
} |
||||
|
|
||||
|
// Stream I/O functions.
|
||||
|
// Provides templated stream I/O for all STL collections so they can be shifted on to any iostream-like interface.
|
||||
|
|
||||
|
template <class S, class T> struct StreamOut { static S& bypass(S& _out, T const& _t) { _out << _t; return _out; } }; |
||||
|
template <class S> struct StreamOut<S, uint8_t> { static S& bypass(S& _out, uint8_t const& _t) { _out << (int)_t; return _out; } }; |
||||
|
|
||||
|
template <class S, class T> |
||||
|
inline S& streamout(S& _out, std::vector<T> const& _e) |
||||
|
{ |
||||
|
_out << "["; |
||||
|
if (!_e.empty()) |
||||
|
{ |
||||
|
StreamOut<S, T>::bypass(_out, _e.front()); |
||||
|
for (auto i = ++_e.begin(); i != _e.end(); ++i) |
||||
|
StreamOut<S, T>::bypass(_out << ",", *i); |
||||
|
} |
||||
|
_out << "]"; |
||||
|
return _out; |
||||
|
} |
||||
|
|
||||
|
template <class T> inline std::ostream& operator<<(std::ostream& _out, std::vector<T> const& _e) { streamout(_out, _e); return _out; } |
||||
|
|
||||
|
template <class S, class T, unsigned Z> |
||||
|
inline S& streamout(S& _out, std::array<T, Z> const& _e) |
||||
|
{ |
||||
|
_out << "["; |
||||
|
if (!_e.empty()) |
||||
|
{ |
||||
|
StreamOut<S, T>::bypass(_out, _e.front()); |
||||
|
auto i = _e.begin(); |
||||
|
for (++i; i != _e.end(); ++i) |
||||
|
StreamOut<S, T>::bypass(_out << ",", *i); |
||||
|
} |
||||
|
_out << "]"; |
||||
|
return _out; |
||||
|
} |
||||
|
template <class T, unsigned Z> inline std::ostream& operator<<(std::ostream& _out, std::array<T, Z> const& _e) { streamout(_out, _e); return _out; } |
||||
|
|
||||
|
template <class S, class T, unsigned long Z> |
||||
|
inline S& streamout(S& _out, std::array<T, Z> const& _e) |
||||
|
{ |
||||
|
_out << "["; |
||||
|
if (!_e.empty()) |
||||
|
{ |
||||
|
StreamOut<S, T>::bypass(_out, _e.front()); |
||||
|
auto i = _e.begin(); |
||||
|
for (++i; i != _e.end(); ++i) |
||||
|
StreamOut<S, T>::bypass(_out << ",", *i); |
||||
|
} |
||||
|
_out << "]"; |
||||
|
return _out; |
||||
|
} |
||||
|
template <class T, unsigned long Z> inline std::ostream& operator<<(std::ostream& _out, std::array<T, Z> const& _e) { streamout(_out, _e); return _out; } |
||||
|
|
||||
|
template <class S, class T> |
||||
|
inline S& streamout(S& _out, std::list<T> const& _e) |
||||
|
{ |
||||
|
_out << "["; |
||||
|
if (!_e.empty()) |
||||
|
{ |
||||
|
_out << _e.front(); |
||||
|
for (auto i = ++_e.begin(); i != _e.end(); ++i) |
||||
|
_out << "," << *i; |
||||
|
} |
||||
|
_out << "]"; |
||||
|
return _out; |
||||
|
} |
||||
|
template <class T> inline std::ostream& operator<<(std::ostream& _out, std::list<T> const& _e) { streamout(_out, _e); return _out; } |
||||
|
|
||||
|
template <class S, class T, class U> |
||||
|
inline S& streamout(S& _out, std::pair<T, U> const& _e) |
||||
|
{ |
||||
|
_out << "(" << _e.first << "," << _e.second << ")"; |
||||
|
return _out; |
||||
|
} |
||||
|
template <class T, class U> inline std::ostream& operator<<(std::ostream& _out, std::pair<T, U> const& _e) { streamout(_out, _e); return _out; } |
||||
|
|
||||
|
template <class S, class T1, class T2, class T3> |
||||
|
inline S& streamout(S& _out, std::tuple<T1, T2, T3> const& _t) |
||||
|
{ |
||||
|
_out << "(" << std::get<0>(_t) << "," << std::get<1>(_t) << "," << std::get<2>(_t) << ")"; |
||||
|
return _out; |
||||
|
} |
||||
|
template <class T1, class T2, class T3> inline std::ostream& operator<<(std::ostream& _out, std::tuple<T1, T2, T3> const& _e) { streamout(_out, _e); return _out; } |
||||
|
|
||||
|
template <class S, class T, class U> |
||||
|
S& streamout(S& _out, std::map<T, U> const& _v) |
||||
|
{ |
||||
|
if (_v.empty()) |
||||
|
return _out << "{}"; |
||||
|
int i = 0; |
||||
|
for (auto p: _v) |
||||
|
_out << (!(i++) ? "{ " : "; ") << p.first << " => " << p.second; |
||||
|
return _out << " }"; |
||||
|
} |
||||
|
template <class T, class U> inline std::ostream& operator<<(std::ostream& _out, std::map<T, U> const& _e) { streamout(_out, _e); return _out; } |
||||
|
|
||||
|
template <class S, class T, class U> |
||||
|
S& streamout(S& _out, std::unordered_map<T, U> const& _v) |
||||
|
{ |
||||
|
if (_v.empty()) |
||||
|
return _out << "{}"; |
||||
|
int i = 0; |
||||
|
for (auto p: _v) |
||||
|
_out << (!(i++) ? "{ " : "; ") << p.first << " => " << p.second; |
||||
|
return _out << " }"; |
||||
|
} |
||||
|
template <class T, class U> inline std::ostream& operator<<(std::ostream& _out, std::unordered_map<T, U> const& _e) { streamout(_out, _e); return _out; } |
||||
|
|
||||
|
template <class S, class T> |
||||
|
S& streamout(S& _out, std::set<T> const& _v) |
||||
|
{ |
||||
|
if (_v.empty()) |
||||
|
return _out << "{}"; |
||||
|
int i = 0; |
||||
|
for (auto p: _v) |
||||
|
_out << (!(i++) ? "{ " : ", ") << p; |
||||
|
return _out << " }"; |
||||
|
} |
||||
|
template <class T> inline std::ostream& operator<<(std::ostream& _out, std::set<T> const& _e) { streamout(_out, _e); return _out; } |
||||
|
|
||||
|
template <class S, class T> |
||||
|
S& streamout(S& _out, std::unordered_set<T> const& _v) |
||||
|
{ |
||||
|
if (_v.empty()) |
||||
|
return _out << "{}"; |
||||
|
int i = 0; |
||||
|
for (auto p: _v) |
||||
|
_out << (!(i++) ? "{ " : ", ") << p; |
||||
|
return _out << " }"; |
||||
|
} |
||||
|
template <class T> inline std::ostream& operator<<(std::ostream& _out, std::unordered_set<T> const& _e) { streamout(_out, _e); return _out; } |
||||
|
|
||||
|
template <class S, class T> |
||||
|
S& streamout(S& _out, std::multiset<T> const& _v) |
||||
|
{ |
||||
|
if (_v.empty()) |
||||
|
return _out << "{}"; |
||||
|
int i = 0; |
||||
|
for (auto p: _v) |
||||
|
_out << (!(i++) ? "{ " : ", ") << p; |
||||
|
return _out << " }"; |
||||
|
} |
||||
|
template <class T> inline std::ostream& operator<<(std::ostream& _out, std::multiset<T> const& _e) { streamout(_out, _e); return _out; } |
||||
|
|
||||
|
template <class S, class T, class U> |
||||
|
S& streamout(S& _out, std::multimap<T, U> const& _v) |
||||
|
{ |
||||
|
if (_v.empty()) |
||||
|
return _out << "{}"; |
||||
|
T l; |
||||
|
int i = 0; |
||||
|
for (auto p: _v) |
||||
|
if (!(i++)) |
||||
|
_out << "{ " << (l = p.first) << " => " << p.second; |
||||
|
else if (l == p.first) |
||||
|
_out << ", " << p.second; |
||||
|
else |
||||
|
_out << "; " << (l = p.first) << " => " << p.second; |
||||
|
return _out << " }"; |
||||
|
} |
||||
|
template <class T, class U> inline std::ostream& operator<<(std::ostream& _out, std::multimap<T, U> const& _e) { streamout(_out, _e); return _out; } |
||||
|
|
||||
|
template <class _S, class _T> _S& operator<<(_S& _out, std::shared_ptr<_T> const& _p) { if (_p) _out << "@" << (*_p); else _out << "nullptr"; return _out; } |
||||
|
|
||||
|
} |
@ -0,0 +1,25 @@ |
|||||
|
/*
|
||||
|
This file is part of cpp-ethereum. |
||||
|
|
||||
|
cpp-ethereum is free software: you can redistribute it and/or modify |
||||
|
it under the terms of the GNU General Public License as published by |
||||
|
the Free Software Foundation, either version 3 of the License, or |
||||
|
(at your option) any later version. |
||||
|
|
||||
|
cpp-ethereum is distributed in the hope that it will be useful, |
||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
GNU General Public License for more details. |
||||
|
|
||||
|
You should have received a copy of the GNU General Public License |
||||
|
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
*/ |
||||
|
/** @file FixedHash.cpp
|
||||
|
* @author Gav Wood <i@gavwood.com> |
||||
|
* @date 2014 |
||||
|
*/ |
||||
|
|
||||
|
#include "FixedHash.h" |
||||
|
|
||||
|
using namespace std; |
||||
|
using namespace eth; |
@ -0,0 +1,195 @@ |
|||||
|
/*
|
||||
|
This file is part of cpp-ethereum. |
||||
|
|
||||
|
cpp-ethereum is free software: you can redistribute it and/or modify |
||||
|
it under the terms of the GNU General Public License as published by |
||||
|
the Free Software Foundation, either version 3 of the License, or |
||||
|
(at your option) any later version. |
||||
|
|
||||
|
cpp-ethereum is distributed in the hope that it will be useful, |
||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
GNU General Public License for more details. |
||||
|
|
||||
|
You should have received a copy of the GNU General Public License |
||||
|
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
*/ |
||||
|
/** @file FixedHash.h
|
||||
|
* @author Gav Wood <i@gavwood.com> |
||||
|
* @date 2014 |
||||
|
* |
||||
|
* The FixedHash fixed-size "hash" container type. |
||||
|
*/ |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <array> |
||||
|
#include <algorithm> |
||||
|
#include "CommonData.h" |
||||
|
|
||||
|
namespace eth |
||||
|
{ |
||||
|
|
||||
|
/// Fixed-size raw-byte array container type, with an API optimised for storing hashes.
|
||||
|
/// Transparently converts to/from the corresponding arithmetic type; this will
|
||||
|
/// assume the data contained in the hash is big-endian.
|
||||
|
template <unsigned N> |
||||
|
class FixedHash |
||||
|
{ |
||||
|
/// The corresponding arithmetic type.
|
||||
|
using Arith = boost::multiprecision::number<boost::multiprecision::cpp_int_backend<N * 8, N * 8, boost::multiprecision::unsigned_magnitude, boost::multiprecision::unchecked, void>>; |
||||
|
|
||||
|
public: |
||||
|
/// The size of the container.
|
||||
|
enum { size = N }; |
||||
|
|
||||
|
/// A dummy flag to avoid accidental construction from pointer.
|
||||
|
enum ConstructFromPointerType { ConstructFromPointer }; |
||||
|
|
||||
|
/// Method to convert from a string.
|
||||
|
enum ConstructFromStringType { FromHex, FromBinary }; |
||||
|
|
||||
|
/// Construct an empty hash.
|
||||
|
FixedHash() { m_data.fill(0); } |
||||
|
|
||||
|
/// Convert from the corresponding arithmetic type.
|
||||
|
FixedHash(Arith const& _arith) { toBigEndian(_arith, m_data); } |
||||
|
|
||||
|
/// Explicitly construct, copying from a byte array.
|
||||
|
explicit FixedHash(bytes const& _b) { if (_b.size() == N) memcpy(m_data.data(), _b.data(), std::min<uint>(_b.size(), N)); } |
||||
|
|
||||
|
/// Explicitly construct, copying from a bytes in memory with given pointer.
|
||||
|
explicit FixedHash(byte const* _bs, ConstructFromPointerType) { memcpy(m_data.data(), _bs, N); } |
||||
|
|
||||
|
/// Explicitly construct, copying from a string.
|
||||
|
explicit FixedHash(std::string const& _s, ConstructFromStringType _t = FromHex): FixedHash(_t == FromHex ? fromHex(_s) : eth::asBytes(_s)) {} |
||||
|
|
||||
|
/// Convert to arithmetic type.
|
||||
|
operator Arith() const { return fromBigEndian<Arith>(m_data); } |
||||
|
|
||||
|
/// @returns true iff this is the empty hash.
|
||||
|
operator bool() const { return ((Arith)*this) != 0; } |
||||
|
|
||||
|
// The obvious comparison operators.
|
||||
|
bool operator==(FixedHash const& _c) const { return m_data == _c.m_data; } |
||||
|
bool operator!=(FixedHash const& _c) const { return m_data != _c.m_data; } |
||||
|
bool operator<(FixedHash const& _c) const { return m_data < _c.m_data; } |
||||
|
|
||||
|
// The obvious binary operators.
|
||||
|
FixedHash& operator^=(FixedHash const& _c) { for (auto i = 0; i < N; ++i) m_data[i] ^= _c.m_data[i]; return *this; } |
||||
|
FixedHash operator^(FixedHash const& _c) const { return FixedHash(*this) ^= _c; } |
||||
|
FixedHash& operator|=(FixedHash const& _c) { for (auto i = 0; i < N; ++i) m_data[i] |= _c.m_data[i]; return *this; } |
||||
|
FixedHash operator|(FixedHash const& _c) const { return FixedHash(*this) |= _c; } |
||||
|
FixedHash& operator&=(FixedHash const& _c) { for (auto i = 0; i < N; ++i) m_data[i] &= _c.m_data[i]; return *this; } |
||||
|
FixedHash operator&(FixedHash const& _c) const { return FixedHash(*this) &= _c; } |
||||
|
FixedHash& operator~() { for (auto i = 0; i < N; ++i) m_data[i] = ~m_data[i]; return *this; } |
||||
|
|
||||
|
/// @returns a particular byte from the hash.
|
||||
|
byte& operator[](unsigned _i) { return m_data[_i]; } |
||||
|
/// @returns a particular byte from the hash.
|
||||
|
byte operator[](unsigned _i) const { return m_data[_i]; } |
||||
|
|
||||
|
/// @returns an abridged version of the hash as a user-readable hex string.
|
||||
|
std::string abridged() const { return toHex(ref().cropped(0, 4)) + ".."; } |
||||
|
|
||||
|
/// @returns a mutable byte vector_ref to the object's data.
|
||||
|
bytesRef ref() { return bytesRef(m_data.data(), N); } |
||||
|
|
||||
|
/// @returns a constant byte vector_ref to the object's data.
|
||||
|
bytesConstRef ref() const { return bytesConstRef(m_data.data(), N); } |
||||
|
|
||||
|
/// @returns a mutable byte pointer to the object's data.
|
||||
|
byte* data() { return m_data.data(); } |
||||
|
|
||||
|
/// @returns a constant byte pointer to the object's data.
|
||||
|
byte const* data() const { return m_data.data(); } |
||||
|
|
||||
|
/// @returns a copy of the object's data as a byte vector.
|
||||
|
bytes asBytes() const { return bytes(data(), data() + N); } |
||||
|
|
||||
|
/// @returns a mutable reference to the object's data as an STL array.
|
||||
|
std::array<byte, N>& asArray() { return m_data; } |
||||
|
|
||||
|
/// @returns a constant reference to the object's data as an STL array.
|
||||
|
std::array<byte, N> const& asArray() const { return m_data; } |
||||
|
|
||||
|
/// A generic std::hash compatible function object.
|
||||
|
struct hash |
||||
|
{ |
||||
|
/// Make a hash of the object's data.
|
||||
|
size_t operator()(FixedHash const& value) const |
||||
|
{ |
||||
|
size_t h = 0; |
||||
|
for (auto i: value.m_data) |
||||
|
h = (h << 5 - h) + i; |
||||
|
return h; |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
private: |
||||
|
std::array<byte, N> m_data; ///< The binary data.
|
||||
|
}; |
||||
|
|
||||
|
|
||||
|
/// Fast equality operator for h256.
|
||||
|
template<> inline bool FixedHash<32>::operator==(FixedHash<32> const& _other) const |
||||
|
{ |
||||
|
const uint64_t* hash1 = (const uint64_t*)this->data(); |
||||
|
const uint64_t* hash2 = (const uint64_t*)_other.data(); |
||||
|
return (hash1[0] == hash2[0]) && (hash1[1] == hash2[1]) && (hash1[2] == hash2[2]) && (hash1[3] == hash2[3]); |
||||
|
} |
||||
|
|
||||
|
/// Fast std::hash compatible hash function object for h256.
|
||||
|
template<> inline size_t FixedHash<32>::hash::operator()(FixedHash<32> const& value) const |
||||
|
{ |
||||
|
const uint64_t*data = (const uint64_t*)value.data(); |
||||
|
uint64_t hash = data[0]; |
||||
|
hash ^= data[1]; |
||||
|
hash ^= data[2]; |
||||
|
hash ^= data[3]; |
||||
|
return (size_t)hash; |
||||
|
} |
||||
|
|
||||
|
/// Stream I/O for the FixedHash class.
|
||||
|
template <unsigned N> |
||||
|
inline std::ostream& operator<<(std::ostream& _out, FixedHash<N> const& _h) |
||||
|
{ |
||||
|
_out << std::noshowbase << std::hex << std::setfill('0'); |
||||
|
for (unsigned i = 0; i < N; ++i) |
||||
|
_out << std::setw(2) << (int)_h[i]; |
||||
|
_out << std::dec; |
||||
|
return _out; |
||||
|
} |
||||
|
|
||||
|
// Common types of FixedHash.
|
||||
|
using h512 = FixedHash<64>; |
||||
|
using h256 = FixedHash<32>; |
||||
|
using h160 = FixedHash<20>; |
||||
|
using h256s = std::vector<h256>; |
||||
|
using h160s = std::vector<h160>; |
||||
|
using h256Set = std::set<h256>; |
||||
|
using h160Set = std::set<h160>; |
||||
|
|
||||
|
/// Convert the given value into h160 (160-bit unsigned integer) using the right 20 bytes.
|
||||
|
inline h160 right160(h256 const& _t) |
||||
|
{ |
||||
|
h160 ret; |
||||
|
memcpy(ret.data(), _t.data() + 12, 20); |
||||
|
return ret; |
||||
|
} |
||||
|
|
||||
|
/// Convert the given value into h160 (160-bit unsigned integer) using the left 20 bytes.
|
||||
|
inline h160 left160(h256 const& _t) |
||||
|
{ |
||||
|
h160 ret; |
||||
|
memcpy(&ret[0], _t.data(), 20); |
||||
|
return ret; |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
namespace std |
||||
|
{ |
||||
|
/// Forward std::hash<eth::h256> to eth::h256::hash.
|
||||
|
template<> struct hash<eth::h256>: eth::h256::hash {}; |
||||
|
} |
@ -0,0 +1,41 @@ |
|||||
|
/*
|
||||
|
This file is part of cpp-ethereum. |
||||
|
|
||||
|
cpp-ethereum is free software: you can redistribute it and/or modify |
||||
|
it under the terms of the GNU General Public License as published by |
||||
|
the Free Software Foundation, either version 3 of the License, or |
||||
|
(at your option) any later version. |
||||
|
|
||||
|
cpp-ethereum is distributed in the hope that it will be useful, |
||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
GNU General Public License for more details. |
||||
|
|
||||
|
You should have received a copy of the GNU General Public License |
||||
|
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
*/ |
||||
|
/** @file Log.cpp
|
||||
|
* @author Gav Wood <i@gavwood.com> |
||||
|
* @date 2014 |
||||
|
*/ |
||||
|
|
||||
|
#include "Log.h" |
||||
|
|
||||
|
#include <string> |
||||
|
#include <iostream> |
||||
|
using namespace std; |
||||
|
using namespace eth; |
||||
|
|
||||
|
// Logging
|
||||
|
int eth::g_logVerbosity = 8; |
||||
|
map<type_info const*, bool> eth::g_logOverride; |
||||
|
|
||||
|
ThreadLocalLogName eth::t_logThreadName("main"); |
||||
|
|
||||
|
void eth::simpleDebugOut(std::string const& _s, char const*) |
||||
|
{ |
||||
|
cout << _s << endl << flush; |
||||
|
} |
||||
|
|
||||
|
std::function<void(std::string const&, char const*)> eth::g_logPost = simpleDebugOut; |
||||
|
|
@ -0,0 +1,134 @@ |
|||||
|
/*
|
||||
|
This file is part of cpp-ethereum. |
||||
|
|
||||
|
cpp-ethereum is free software: you can redistribute it and/or modify |
||||
|
it under the terms of the GNU General Public License as published by |
||||
|
the Free Software Foundation, either version 3 of the License, or |
||||
|
(at your option) any later version. |
||||
|
|
||||
|
cpp-ethereum is distributed in the hope that it will be useful, |
||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
GNU General Public License for more details. |
||||
|
|
||||
|
You should have received a copy of the GNU General Public License |
||||
|
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
*/ |
||||
|
/** @file Log.h
|
||||
|
* @author Gav Wood <i@gavwood.com> |
||||
|
* @date 2014 |
||||
|
* |
||||
|
* The logging subsystem. |
||||
|
*/ |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <ctime> |
||||
|
#include <chrono> |
||||
|
#include <boost/thread.hpp> |
||||
|
#include "vector_ref.h" |
||||
|
|
||||
|
namespace eth |
||||
|
{ |
||||
|
|
||||
|
/// The null output stream. Used when logging is disabled.
|
||||
|
class NullOutputStream |
||||
|
{ |
||||
|
public: |
||||
|
template <class T> NullOutputStream& operator<<(T const&) { return *this; } |
||||
|
}; |
||||
|
|
||||
|
/// A simple log-output function that prints log messages to stdout.
|
||||
|
void simpleDebugOut(std::string const&, char const* ); |
||||
|
|
||||
|
/// The logging system's current verbosity.
|
||||
|
extern int g_logVerbosity; |
||||
|
|
||||
|
/// The current method that the logging system uses to output the log messages. Defaults to simpleDebugOut().
|
||||
|
extern std::function<void(std::string const&, char const*)> g_logPost; |
||||
|
|
||||
|
/// Map of Log Channel types to bool, false forces the channel to be disabled, true forces it to be enabled.
|
||||
|
/// If a channel has no entry, then it will output as long as its verbosity (LogChannel::verbosity) is less than
|
||||
|
/// or equal to the currently output verbosity (g_logVerbosity).
|
||||
|
extern std::map<std::type_info const*, bool> g_logOverride; |
||||
|
|
||||
|
/// Associate a name with each thread for nice logging.
|
||||
|
struct ThreadLocalLogName |
||||
|
{ |
||||
|
ThreadLocalLogName(std::string _name) { m_name.reset(new std::string(_name)); }; |
||||
|
boost::thread_specific_ptr<std::string> m_name; |
||||
|
}; |
||||
|
|
||||
|
/// The current thread's name.
|
||||
|
extern ThreadLocalLogName t_logThreadName; |
||||
|
|
||||
|
/// Set the current thread's log name.
|
||||
|
inline void setThreadName(char const* _n) { t_logThreadName.m_name.reset(new std::string(_n)); } |
||||
|
|
||||
|
/// The default logging channels. Each has an associated verbosity and three-letter prefix (name() ).
|
||||
|
/// Channels should inherit from LogChannel and define name() and verbosity.
|
||||
|
struct LogChannel { static const char* name() { return " "; } static const int verbosity = 1; }; |
||||
|
struct LeftChannel: public LogChannel { static const char* name() { return "<<<"; } }; |
||||
|
struct RightChannel: public LogChannel { static const char* name() { return ">>>"; } }; |
||||
|
struct WarnChannel: public LogChannel { static const char* name() { return "!!!"; } static const int verbosity = 0; }; |
||||
|
struct NoteChannel: public LogChannel { static const char* name() { return "***"; } }; |
||||
|
struct DebugChannel: public LogChannel { static const char* name() { return "---"; } static const int verbosity = 0; }; |
||||
|
|
||||
|
/// Logging class, iostream-like, that can be shifted to.
|
||||
|
template <class Id, bool _AutoSpacing = true> |
||||
|
class LogOutputStream |
||||
|
{ |
||||
|
public: |
||||
|
/// Construct a new object.
|
||||
|
/// If _term is true the the prefix info is terminated with a ']' character; if not it ends only with a '|' character.
|
||||
|
LogOutputStream(bool _term = true) |
||||
|
{ |
||||
|
std::type_info const* i = &typeid(Id); |
||||
|
auto it = g_logOverride.find(i); |
||||
|
if ((it != g_logOverride.end() && it->second == true) || (it == g_logOverride.end() && Id::verbosity <= g_logVerbosity)) |
||||
|
{ |
||||
|
time_t rawTime = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); |
||||
|
char buf[24]; |
||||
|
if (strftime(buf, 24, "%X", localtime(&rawTime)) == 0) |
||||
|
buf[0] = '\0'; // empty if case strftime fails
|
||||
|
m_sstr << Id::name() << " [ " << buf << " | " << *(t_logThreadName.m_name.get()) << (_term ? " ] " : ""); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// Destructor. Posts the accrued log entry to the g_logPost function.
|
||||
|
~LogOutputStream() { if (Id::verbosity <= g_logVerbosity) g_logPost(m_sstr.str(), Id::name()); } |
||||
|
|
||||
|
/// Shift arbitrary data to the log. Spaces will be added between items as required.
|
||||
|
template <class T> LogOutputStream& operator<<(T const& _t) { if (Id::verbosity <= g_logVerbosity) { if (_AutoSpacing && m_sstr.str().size() && m_sstr.str().back() != ' ') m_sstr << " "; m_sstr << _t; } return *this; } |
||||
|
|
||||
|
private: |
||||
|
std::stringstream m_sstr; ///< The accrued log entry.
|
||||
|
}; |
||||
|
|
||||
|
// Simple cout-like stream objects for accessing common log channels.
|
||||
|
// Dirties the global namespace, but oh so convenient...
|
||||
|
#define cnote eth::LogOutputStream<eth::NoteChannel, true>() |
||||
|
#define cwarn eth::LogOutputStream<eth::WarnChannel, true>() |
||||
|
|
||||
|
// Null stream-like objects.
|
||||
|
#define ndebug if (true) {} else eth::NullOutputStream() |
||||
|
#define nlog(X) if (true) {} else eth::NullOutputStream() |
||||
|
#define nslog(X) if (true) {} else eth::NullOutputStream() |
||||
|
|
||||
|
// Kill debugging log channel when we're in release mode.
|
||||
|
#if NDEBUG |
||||
|
#define cdebug ndebug |
||||
|
#else |
||||
|
#define cdebug eth::LogOutputStream<eth::DebugChannel, true>() |
||||
|
#endif |
||||
|
|
||||
|
// Kill all logs when when NLOG is defined.
|
||||
|
#if NLOG |
||||
|
#define clog(X) nlog(X) |
||||
|
#define cslog(X) nslog(X) |
||||
|
#else |
||||
|
#define clog(X) eth::LogOutputStream<X, true>() |
||||
|
#define cslog(X) eth::LogOutputStream<X, false>() |
||||
|
#endif |
||||
|
|
||||
|
} |
File diff suppressed because it is too large
@ -0,0 +1,542 @@ |
|||||
|
/*
|
||||
|
This file is part of cpp-ethereum. |
||||
|
|
||||
|
cpp-ethereum is free software: you can redistribute it and/or modify |
||||
|
it under the terms of the GNU General Public License as published by |
||||
|
the Free Software Foundation, either version 3 of the License, or |
||||
|
(at your option) any later version. |
||||
|
|
||||
|
cpp-ethereum is distributed in the hope that it will be useful, |
||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
GNU General Public License for more details. |
||||
|
|
||||
|
You should have received a copy of the GNU General Public License |
||||
|
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
*/ |
||||
|
/** @file PeerNetwork.cpp
|
||||
|
* @authors: |
||||
|
* Gav Wood <i@gavwood.com> |
||||
|
* Eric Lombrozo <elombrozo@gmail.com> |
||||
|
* @date 2014 |
||||
|
*/ |
||||
|
|
||||
|
#include "PeerServer.h" |
||||
|
|
||||
|
#include <sys/types.h> |
||||
|
#ifdef _WIN32 |
||||
|
// winsock is already included
|
||||
|
// #include <winsock.h>
|
||||
|
#else |
||||
|
#include <ifaddrs.h> |
||||
|
#endif |
||||
|
|
||||
|
#include <set> |
||||
|
#include <chrono> |
||||
|
#include <thread> |
||||
|
#include "Exceptions.h" |
||||
|
#include "Common.h" |
||||
|
#include "BlockChain.h" |
||||
|
#include "BlockInfo.h" |
||||
|
#include "TransactionQueue.h" |
||||
|
#include "UPnP.h" |
||||
|
#include "PeerSession.h" |
||||
|
using namespace std; |
||||
|
using namespace eth; |
||||
|
|
||||
|
// Addresses we will skip during network interface discovery
|
||||
|
// Use a vector as the list is small
|
||||
|
// Why this and not names?
|
||||
|
// Under MacOSX loopback (127.0.0.1) can be named lo0 and br0 are bridges (0.0.0.0)
|
||||
|
static const set<bi::address> c_rejectAddresses = { |
||||
|
{bi::address_v4::from_string("127.0.0.1")}, |
||||
|
{bi::address_v6::from_string("::1")}, |
||||
|
{bi::address_v4::from_string("0.0.0.0")}, |
||||
|
{bi::address_v6::from_string("::")} |
||||
|
}; |
||||
|
|
||||
|
PeerServer::PeerServer(std::string const& _clientVersion, BlockChain const& _ch, uint _networkId, unsigned short _port, NodeMode _m, string const& _publicAddress, bool _upnp): |
||||
|
m_clientVersion(_clientVersion), |
||||
|
m_mode(_m), |
||||
|
m_listenPort(_port), |
||||
|
m_chain(&_ch), |
||||
|
m_acceptor(m_ioService, bi::tcp::endpoint(bi::tcp::v4(), _port)), |
||||
|
m_socket(m_ioService), |
||||
|
m_key(KeyPair::create()), |
||||
|
m_networkId(_networkId) |
||||
|
{ |
||||
|
populateAddresses(); |
||||
|
determinePublic(_publicAddress, _upnp); |
||||
|
ensureAccepting(); |
||||
|
clog(NetNote) << "Id:" << toHex(m_key.address().ref().cropped(0, 4)) << "Mode: " << (_m == NodeMode::PeerServer ? "PeerServer" : "Full"); |
||||
|
} |
||||
|
|
||||
|
PeerServer::PeerServer(std::string const& _clientVersion, uint _networkId, NodeMode _m): |
||||
|
m_clientVersion(_clientVersion), |
||||
|
m_mode(_m), |
||||
|
m_listenPort(0), |
||||
|
m_acceptor(m_ioService, bi::tcp::endpoint(bi::tcp::v4(), 0)), |
||||
|
m_socket(m_ioService), |
||||
|
m_key(KeyPair::create()), |
||||
|
m_networkId(_networkId) |
||||
|
{ |
||||
|
// populate addresses.
|
||||
|
populateAddresses(); |
||||
|
clog(NetNote) << "Id:" << toHex(m_key.address().ref().cropped(0, 4)) << "Mode: " << (m_mode == NodeMode::PeerServer ? "PeerServer" : "Full"); |
||||
|
} |
||||
|
|
||||
|
PeerServer::~PeerServer() |
||||
|
{ |
||||
|
for (auto const& i: m_peers) |
||||
|
if (auto p = i.second.lock()) |
||||
|
p->disconnect(ClientQuit); |
||||
|
delete m_upnp; |
||||
|
} |
||||
|
|
||||
|
unsigned PeerServer::protocolVersion() |
||||
|
{ |
||||
|
return 8; |
||||
|
} |
||||
|
|
||||
|
void PeerServer::determinePublic(string const& _publicAddress, bool _upnp) |
||||
|
{ |
||||
|
if (_upnp) |
||||
|
try |
||||
|
{ |
||||
|
m_upnp = new UPnP; |
||||
|
} |
||||
|
catch (NoUPnPDevice) {} // let m_upnp continue as null - we handle it properly.
|
||||
|
|
||||
|
bi::tcp::resolver r(m_ioService); |
||||
|
if (m_upnp && m_upnp->isValid() && m_peerAddresses.size()) |
||||
|
{ |
||||
|
clog(NetNote) << "External addr: " << m_upnp->externalIP(); |
||||
|
int p = m_upnp->addRedirect(m_peerAddresses[0].to_string().c_str(), m_listenPort); |
||||
|
if (p) |
||||
|
clog(NetNote) << "Punched through NAT and mapped local port" << m_listenPort << "onto external port" << p << "."; |
||||
|
else |
||||
|
{ |
||||
|
// couldn't map
|
||||
|
clog(NetWarn) << "Couldn't punch through NAT (or no NAT in place). Assuming " << m_listenPort << " is local & external port."; |
||||
|
p = m_listenPort; |
||||
|
} |
||||
|
|
||||
|
auto eip = m_upnp->externalIP(); |
||||
|
if (eip == string("0.0.0.0") && _publicAddress.empty()) |
||||
|
m_public = bi::tcp::endpoint(bi::address(), (unsigned short)p); |
||||
|
else |
||||
|
{ |
||||
|
m_public = bi::tcp::endpoint(bi::address::from_string(_publicAddress.empty() ? eip : _publicAddress), (unsigned short)p); |
||||
|
m_addresses.push_back(m_public.address().to_v4()); |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
// No UPnP - fallback on given public address or, if empty, the assumed peer address.
|
||||
|
m_public = bi::tcp::endpoint(_publicAddress.size() ? bi::address::from_string(_publicAddress) |
||||
|
: m_peerAddresses.size() ? m_peerAddresses[0] |
||||
|
: bi::address(), m_listenPort); |
||||
|
m_addresses.push_back(m_public.address().to_v4()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void PeerServer::populateAddresses() |
||||
|
{ |
||||
|
#ifdef _WIN32 |
||||
|
WSAData wsaData; |
||||
|
if (WSAStartup(MAKEWORD(1, 1), &wsaData) != 0) |
||||
|
throw NoNetworking(); |
||||
|
|
||||
|
char ac[80]; |
||||
|
if (gethostname(ac, sizeof(ac)) == SOCKET_ERROR) |
||||
|
{ |
||||
|
clog(NetWarn) << "Error " << WSAGetLastError() << " when getting local host name."; |
||||
|
WSACleanup(); |
||||
|
throw NoNetworking(); |
||||
|
} |
||||
|
|
||||
|
struct hostent* phe = gethostbyname(ac); |
||||
|
if (phe == 0) |
||||
|
{ |
||||
|
clog(NetWarn) << "Bad host lookup."; |
||||
|
WSACleanup(); |
||||
|
throw NoNetworking(); |
||||
|
} |
||||
|
|
||||
|
for (int i = 0; phe->h_addr_list[i] != 0; ++i) |
||||
|
{ |
||||
|
struct in_addr addr; |
||||
|
memcpy(&addr, phe->h_addr_list[i], sizeof(struct in_addr)); |
||||
|
char *addrStr = inet_ntoa(addr); |
||||
|
bi::address ad(bi::address::from_string(addrStr)); |
||||
|
m_addresses.push_back(ad.to_v4()); |
||||
|
bool isLocal = std::find(c_rejectAddresses.begin(), c_rejectAddresses.end(), ad) != c_rejectAddresses.end(); |
||||
|
if (!isLocal) |
||||
|
m_peerAddresses.push_back(ad.to_v4()); |
||||
|
clog(NetNote) << "Address: " << ac << " = " << m_addresses.back() << (isLocal ? " [LOCAL]" : " [PEER]"); |
||||
|
} |
||||
|
|
||||
|
WSACleanup(); |
||||
|
#else |
||||
|
ifaddrs* ifaddr; |
||||
|
if (getifaddrs(&ifaddr) == -1) |
||||
|
throw NoNetworking(); |
||||
|
|
||||
|
bi::tcp::resolver r(m_ioService); |
||||
|
|
||||
|
for (ifaddrs* ifa = ifaddr; ifa; ifa = ifa->ifa_next) |
||||
|
{ |
||||
|
if (!ifa->ifa_addr) |
||||
|
continue; |
||||
|
if (ifa->ifa_addr->sa_family == AF_INET) |
||||
|
{ |
||||
|
char host[NI_MAXHOST]; |
||||
|
if (getnameinfo(ifa->ifa_addr, sizeof(struct sockaddr_in), host, NI_MAXHOST, NULL, 0, NI_NUMERICHOST)) |
||||
|
continue; |
||||
|
// TODO: Make exception safe when no internet.
|
||||
|
auto it = r.resolve({host, "30303"}); |
||||
|
bi::tcp::endpoint ep = it->endpoint(); |
||||
|
bi::address ad = ep.address(); |
||||
|
m_addresses.push_back(ad.to_v4()); |
||||
|
bool isLocal = std::find(c_rejectAddresses.begin(), c_rejectAddresses.end(), ad) != c_rejectAddresses.end(); |
||||
|
if (!isLocal) |
||||
|
m_peerAddresses.push_back(ad.to_v4()); |
||||
|
clog(NetNote) << "Address: " << host << " = " << m_addresses.back() << (isLocal ? " [LOCAL]" : " [PEER]"); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
freeifaddrs(ifaddr); |
||||
|
#endif |
||||
|
} |
||||
|
|
||||
|
std::map<Public, bi::tcp::endpoint> PeerServer::potentialPeers() |
||||
|
{ |
||||
|
std::map<Public, bi::tcp::endpoint> ret; |
||||
|
if (!m_public.address().is_unspecified()) |
||||
|
ret.insert(make_pair(m_key.pub(), m_public)); |
||||
|
for (auto i: m_peers) |
||||
|
if (auto j = i.second.lock()) |
||||
|
{ |
||||
|
auto ep = j->endpoint(); |
||||
|
// Skip peers with a listen port of zero or are on a private network
|
||||
|
bool peerOnNet = (j->m_listenPort != 0 && !isPrivateAddress(ep.address())); |
||||
|
if (peerOnNet && ep.port() && j->m_id) |
||||
|
ret.insert(make_pair(i.first, ep)); |
||||
|
} |
||||
|
return ret; |
||||
|
} |
||||
|
|
||||
|
void PeerServer::ensureAccepting() |
||||
|
{ |
||||
|
if (m_accepting == false) |
||||
|
{ |
||||
|
clog(NetNote) << "Listening on local port " << m_listenPort << " (public: " << m_public << ")"; |
||||
|
m_accepting = true; |
||||
|
m_acceptor.async_accept(m_socket, [=](boost::system::error_code ec) |
||||
|
{ |
||||
|
if (!ec) |
||||
|
try |
||||
|
{ |
||||
|
try { |
||||
|
clog(NetNote) << "Accepted connection from " << m_socket.remote_endpoint(); |
||||
|
} catch (...){} |
||||
|
bi::address remoteAddress = m_socket.remote_endpoint().address(); |
||||
|
// Port defaults to 0 - we let the hello tell us which port the peer listens to
|
||||
|
auto p = std::make_shared<PeerSession>(this, std::move(m_socket), m_networkId, remoteAddress); |
||||
|
p->start(); |
||||
|
} |
||||
|
catch (std::exception const& _e) |
||||
|
{ |
||||
|
clog(NetWarn) << "ERROR: " << _e.what(); |
||||
|
} |
||||
|
m_accepting = false; |
||||
|
if (ec.value() != 1 && (m_mode == NodeMode::PeerServer || m_peers.size() < m_idealPeerCount * 2)) |
||||
|
ensureAccepting(); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void PeerServer::connect(std::string const& _addr, unsigned short _port) noexcept |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
connect(bi::tcp::endpoint(bi::address::from_string(_addr), _port)); |
||||
|
} |
||||
|
catch (exception const& e) |
||||
|
{ |
||||
|
// Couldn't connect
|
||||
|
clog(NetNote) << "Bad host " << _addr << " (" << e.what() << ")"; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void PeerServer::connect(bi::tcp::endpoint const& _ep) |
||||
|
{ |
||||
|
clog(NetNote) << "Attempting connection to " << _ep; |
||||
|
bi::tcp::socket* s = new bi::tcp::socket(m_ioService); |
||||
|
s->async_connect(_ep, [=](boost::system::error_code const& ec) |
||||
|
{ |
||||
|
if (ec) |
||||
|
{ |
||||
|
clog(NetNote) << "Connection refused to " << _ep << " (" << ec.message() << ")"; |
||||
|
for (auto i = m_incomingPeers.begin(); i != m_incomingPeers.end(); ++i) |
||||
|
if (i->second.first == _ep && i->second.second < 3) |
||||
|
{ |
||||
|
m_freePeers.push_back(i->first); |
||||
|
goto OK; |
||||
|
} |
||||
|
// for-else
|
||||
|
clog(NetNote) << "Giving up."; |
||||
|
OK:; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
auto p = make_shared<PeerSession>(this, std::move(*s), m_networkId, _ep.address(), _ep.port()); |
||||
|
clog(NetNote) << "Connected to " << _ep; |
||||
|
p->start(); |
||||
|
} |
||||
|
delete s; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
bool PeerServer::sync() |
||||
|
{ |
||||
|
bool ret = false; |
||||
|
if (isInitialised()) |
||||
|
for (auto i = m_peers.begin(); i != m_peers.end();) |
||||
|
{ |
||||
|
auto p = i->second.lock(); |
||||
|
if (p && p->m_socket.is_open() && |
||||
|
(p->m_disconnect == chrono::steady_clock::time_point::max() || chrono::steady_clock::now() - p->m_disconnect < chrono::seconds(1))) // kill old peers that should be disconnected.
|
||||
|
++i; |
||||
|
else |
||||
|
{ |
||||
|
i = m_peers.erase(i); |
||||
|
ret = true; |
||||
|
} |
||||
|
} |
||||
|
return ret; |
||||
|
} |
||||
|
|
||||
|
bool PeerServer::ensureInitialised(BlockChain& _bc, TransactionQueue& _tq) |
||||
|
{ |
||||
|
if (m_latestBlockSent == h256()) |
||||
|
{ |
||||
|
// First time - just initialise.
|
||||
|
m_latestBlockSent = _bc.currentHash(); |
||||
|
clog(NetNote) << "Initialising: latest=" << m_latestBlockSent; |
||||
|
|
||||
|
for (auto const& i: _tq.transactions()) |
||||
|
m_transactionsSent.insert(i.first); |
||||
|
m_lastPeersRequest = chrono::steady_clock::time_point::min(); |
||||
|
return true; |
||||
|
} |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
bool PeerServer::sync(BlockChain& _bc, TransactionQueue& _tq, Overlay& _o) |
||||
|
{ |
||||
|
bool ret = ensureInitialised(_bc, _tq); |
||||
|
|
||||
|
if (sync()) |
||||
|
ret = true; |
||||
|
|
||||
|
if (m_mode == NodeMode::Full) |
||||
|
{ |
||||
|
for (auto it = m_incomingTransactions.begin(); it != m_incomingTransactions.end(); ++it) |
||||
|
if (_tq.import(*it)) |
||||
|
{}//ret = true; // just putting a transaction in the queue isn't enough to change the state - it might have an invalid nonce...
|
||||
|
else |
||||
|
m_transactionsSent.insert(sha3(*it)); // if we already had the transaction, then don't bother sending it on.
|
||||
|
m_incomingTransactions.clear(); |
||||
|
|
||||
|
auto h = _bc.currentHash(); |
||||
|
bool resendAll = (h != m_latestBlockSent); |
||||
|
|
||||
|
// Send any new transactions.
|
||||
|
for (auto j: m_peers) |
||||
|
if (auto p = j.second.lock()) |
||||
|
{ |
||||
|
bytes b; |
||||
|
uint n = 0; |
||||
|
for (auto const& i: _tq.transactions()) |
||||
|
if ((!m_transactionsSent.count(i.first) && !p->m_knownTransactions.count(i.first)) || p->m_requireTransactions || resendAll) |
||||
|
{ |
||||
|
b += i.second; |
||||
|
++n; |
||||
|
m_transactionsSent.insert(i.first); |
||||
|
} |
||||
|
if (n) |
||||
|
{ |
||||
|
RLPStream ts; |
||||
|
PeerSession::prep(ts); |
||||
|
ts.appendList(n + 1) << TransactionsPacket; |
||||
|
ts.appendRaw(b, n).swapOut(b); |
||||
|
seal(b); |
||||
|
p->send(&b); |
||||
|
} |
||||
|
p->m_knownTransactions.clear(); |
||||
|
p->m_requireTransactions = false; |
||||
|
} |
||||
|
|
||||
|
// Send any new blocks.
|
||||
|
if (h != m_latestBlockSent) |
||||
|
{ |
||||
|
// TODO: find where they diverge and send complete new branch.
|
||||
|
RLPStream ts; |
||||
|
PeerSession::prep(ts); |
||||
|
ts.appendList(2) << BlocksPacket; |
||||
|
bytes b; |
||||
|
ts.appendRaw(_bc.block(_bc.currentHash())).swapOut(b); |
||||
|
seal(b); |
||||
|
for (auto j: m_peers) |
||||
|
if (auto p = j.second.lock()) |
||||
|
{ |
||||
|
if (!p->m_knownBlocks.count(_bc.currentHash())) |
||||
|
p->send(&b); |
||||
|
p->m_knownBlocks.clear(); |
||||
|
} |
||||
|
} |
||||
|
m_latestBlockSent = h; |
||||
|
|
||||
|
for (int accepted = 1, n = 0; accepted; ++n) |
||||
|
{ |
||||
|
accepted = 0; |
||||
|
|
||||
|
if (m_incomingBlocks.size()) |
||||
|
for (auto it = prev(m_incomingBlocks.end());; --it) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
_bc.import(*it, _o); |
||||
|
it = m_incomingBlocks.erase(it); |
||||
|
++accepted; |
||||
|
ret = true; |
||||
|
} |
||||
|
catch (UnknownParent) |
||||
|
{ |
||||
|
// Don't (yet) know its parent. Leave it for later.
|
||||
|
m_unknownParentBlocks.push_back(*it); |
||||
|
it = m_incomingBlocks.erase(it); |
||||
|
} |
||||
|
catch (...) |
||||
|
{ |
||||
|
// Some other error - erase it.
|
||||
|
it = m_incomingBlocks.erase(it); |
||||
|
} |
||||
|
|
||||
|
if (it == m_incomingBlocks.begin()) |
||||
|
break; |
||||
|
} |
||||
|
if (!n && accepted) |
||||
|
{ |
||||
|
for (auto i: m_unknownParentBlocks) |
||||
|
m_incomingBlocks.push_back(i); |
||||
|
m_unknownParentBlocks.clear(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Connect to additional peers
|
||||
|
while (m_peers.size() < m_idealPeerCount) |
||||
|
{ |
||||
|
if (m_freePeers.empty()) |
||||
|
{ |
||||
|
if (chrono::steady_clock::now() > m_lastPeersRequest + chrono::seconds(10)) |
||||
|
{ |
||||
|
RLPStream s; |
||||
|
bytes b; |
||||
|
(PeerSession::prep(s).appendList(1) << GetPeersPacket).swapOut(b); |
||||
|
seal(b); |
||||
|
for (auto const& i: m_peers) |
||||
|
if (auto p = i.second.lock()) |
||||
|
if (p->isOpen()) |
||||
|
p->send(&b); |
||||
|
m_lastPeersRequest = chrono::steady_clock::now(); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
if (!m_accepting) |
||||
|
ensureAccepting(); |
||||
|
|
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
auto x = time(0) % m_freePeers.size(); |
||||
|
m_incomingPeers[m_freePeers[x]].second++; |
||||
|
connect(m_incomingPeers[m_freePeers[x]].first); |
||||
|
m_freePeers.erase(m_freePeers.begin() + x); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// platform for consensus of social contract.
|
||||
|
// restricts your freedom but does so fairly. and that's the value proposition.
|
||||
|
// guarantees that everyone else respect the rules of the system. (i.e. obeys laws).
|
||||
|
|
||||
|
// We'll keep at most twice as many as is ideal, halfing what counts as "too young to kill" until we get there.
|
||||
|
for (uint old = 15000; m_peers.size() > m_idealPeerCount * 2 && old > 100; old /= 2) |
||||
|
while (m_peers.size() > m_idealPeerCount) |
||||
|
{ |
||||
|
// look for worst peer to kick off
|
||||
|
// first work out how many are old enough to kick off.
|
||||
|
shared_ptr<PeerSession> worst; |
||||
|
unsigned agedPeers = 0; |
||||
|
for (auto i: m_peers) |
||||
|
if (auto p = i.second.lock()) |
||||
|
if ((m_mode != NodeMode::PeerServer || p->m_caps != 0x01) && chrono::steady_clock::now() > p->m_connect + chrono::milliseconds(old)) // don't throw off new peers; peer-servers should never kick off other peer-servers.
|
||||
|
{ |
||||
|
++agedPeers; |
||||
|
if ((!worst || p->m_rating < worst->m_rating || (p->m_rating == worst->m_rating && p->m_connect > worst->m_connect))) // kill older ones
|
||||
|
worst = p; |
||||
|
} |
||||
|
if (!worst || agedPeers <= m_idealPeerCount) |
||||
|
break; |
||||
|
worst->disconnect(TooManyPeers); |
||||
|
} |
||||
|
|
||||
|
return ret; |
||||
|
} |
||||
|
|
||||
|
std::vector<PeerInfo> PeerServer::peers() const |
||||
|
{ |
||||
|
const_cast<PeerServer*>(this)->pingAll(); |
||||
|
this_thread::sleep_for(chrono::milliseconds(200)); |
||||
|
std::vector<PeerInfo> ret; |
||||
|
for (auto& i: m_peers) |
||||
|
if (auto j = i.second.lock()) |
||||
|
if (j->m_socket.is_open()) |
||||
|
ret.push_back(j->m_info); |
||||
|
return ret; |
||||
|
} |
||||
|
|
||||
|
void PeerServer::pingAll() |
||||
|
{ |
||||
|
for (auto& i: m_peers) |
||||
|
if (auto j = i.second.lock()) |
||||
|
j->ping(); |
||||
|
} |
||||
|
|
||||
|
bytes PeerServer::savePeers() const |
||||
|
{ |
||||
|
RLPStream ret; |
||||
|
int n = 0; |
||||
|
for (auto& i: m_peers) |
||||
|
if (auto p = i.second.lock()) |
||||
|
if (p->m_socket.is_open() && p->endpoint().port()) |
||||
|
{ |
||||
|
ret.appendList(3) << p->endpoint().address().to_v4().to_bytes() << p->endpoint().port() << p->m_id; |
||||
|
n++; |
||||
|
} |
||||
|
return RLPStream(n).appendRaw(ret.out(), n).out(); |
||||
|
} |
||||
|
|
||||
|
void PeerServer::restorePeers(bytesConstRef _b) |
||||
|
{ |
||||
|
for (auto i: RLP(_b)) |
||||
|
{ |
||||
|
auto k = (Public)i[2]; |
||||
|
if (!m_incomingPeers.count(k)) |
||||
|
{ |
||||
|
m_incomingPeers.insert(make_pair(k, make_pair(bi::tcp::endpoint(bi::address_v4(i[0].toArray<byte, 4>()), i[1].toInt<short>()), 0))); |
||||
|
m_freePeers.push_back(k); |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,133 @@ |
|||||
|
/*
|
||||
|
This file is part of cpp-ethereum. |
||||
|
|
||||
|
cpp-ethereum is free software: you can redistribute it and/or modify |
||||
|
it under the terms of the GNU General Public License as published by |
||||
|
the Free Software Foundation, either version 3 of the License, or |
||||
|
(at your option) any later version. |
||||
|
|
||||
|
cpp-ethereum is distributed in the hope that it will be useful, |
||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
GNU General Public License for more details. |
||||
|
|
||||
|
You should have received a copy of the GNU General Public License |
||||
|
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
*/ |
||||
|
/** @file PeerServer.h
|
||||
|
* @author Gav Wood <i@gavwood.com> |
||||
|
* @date 2014 |
||||
|
*/ |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <map> |
||||
|
#include <vector> |
||||
|
#include <set> |
||||
|
#include <memory> |
||||
|
#include <utility> |
||||
|
#include <thread> |
||||
|
#include "PeerNetwork.h" |
||||
|
#include "CommonEth.h" |
||||
|
namespace ba = boost::asio; |
||||
|
namespace bi = boost::asio::ip; |
||||
|
|
||||
|
namespace eth |
||||
|
{ |
||||
|
|
||||
|
class PeerServer |
||||
|
{ |
||||
|
friend class PeerSession; |
||||
|
|
||||
|
public: |
||||
|
/// Start server, listening for connections on the given port.
|
||||
|
PeerServer(std::string const& _clientVersion, BlockChain const& _ch, uint _networkId, unsigned short _port, NodeMode _m = NodeMode::Full, std::string const& _publicAddress = std::string(), bool _upnp = true); |
||||
|
/// Start server, but don't listen.
|
||||
|
PeerServer(std::string const& _clientVersion, uint _networkId, NodeMode _m = NodeMode::Full); |
||||
|
~PeerServer(); |
||||
|
|
||||
|
static unsigned protocolVersion(); |
||||
|
unsigned networkId() { return m_networkId; } |
||||
|
|
||||
|
/// Connect to a peer explicitly.
|
||||
|
void connect(std::string const& _addr, unsigned short _port = 30303) noexcept; |
||||
|
void connect(bi::tcp::endpoint const& _ep); |
||||
|
|
||||
|
/// Sync with the BlockChain. It might contain one of our mined blocks, we might have new candidates from the network.
|
||||
|
bool sync(BlockChain& _bc, TransactionQueue&, Overlay& _o); |
||||
|
bool sync(); |
||||
|
|
||||
|
/// Conduct I/O, polling, syncing, whatever.
|
||||
|
/// Ideally all time-consuming I/O is done in a background thread or otherwise asynchronously, but you get this call every 100ms or so anyway.
|
||||
|
/// This won't touch alter the blockchain.
|
||||
|
void process() { if (isInitialised()) m_ioService.poll(); } |
||||
|
|
||||
|
/// Set ideal number of peers.
|
||||
|
void setIdealPeerCount(unsigned _n) { m_idealPeerCount = _n; } |
||||
|
|
||||
|
void setMode(NodeMode _m) { m_mode = _m; } |
||||
|
|
||||
|
/// Get peer information.
|
||||
|
std::vector<PeerInfo> peers() const; |
||||
|
|
||||
|
/// Get number of peers connected; equivalent to, but faster than, peers().size().
|
||||
|
size_t peerCount() const { return m_peers.size(); } |
||||
|
|
||||
|
/// Ping the peers, to update the latency information.
|
||||
|
void pingAll(); |
||||
|
|
||||
|
/// Get the port we're listening on currently.
|
||||
|
unsigned short listenPort() const { return m_public.port(); } |
||||
|
|
||||
|
bytes savePeers() const; |
||||
|
void restorePeers(bytesConstRef _b); |
||||
|
|
||||
|
private: |
||||
|
void seal(bytes& _b); |
||||
|
void populateAddresses(); |
||||
|
void determinePublic(std::string const& _publicAddress, bool _upnp); |
||||
|
void ensureAccepting(); |
||||
|
|
||||
|
/// Check to see if the network peer-state initialisation has happened.
|
||||
|
bool isInitialised() const { return m_latestBlockSent; } |
||||
|
/// Initialises the network peer-state, doing the stuff that needs to be once-only. @returns true if it really was first.
|
||||
|
bool ensureInitialised(BlockChain& _bc, TransactionQueue& _tq); |
||||
|
|
||||
|
std::map<Public, bi::tcp::endpoint> potentialPeers(); |
||||
|
|
||||
|
std::string m_clientVersion; |
||||
|
NodeMode m_mode = NodeMode::Full; |
||||
|
|
||||
|
unsigned short m_listenPort; |
||||
|
|
||||
|
BlockChain const* m_chain = nullptr; |
||||
|
ba::io_service m_ioService; |
||||
|
bi::tcp::acceptor m_acceptor; |
||||
|
bi::tcp::socket m_socket; |
||||
|
|
||||
|
UPnP* m_upnp = nullptr; |
||||
|
bi::tcp::endpoint m_public; |
||||
|
KeyPair m_key; |
||||
|
|
||||
|
unsigned m_networkId; |
||||
|
std::map<Public, std::weak_ptr<PeerSession>> m_peers; |
||||
|
|
||||
|
std::vector<bytes> m_incomingTransactions; |
||||
|
std::vector<bytes> m_incomingBlocks; |
||||
|
std::vector<bytes> m_unknownParentBlocks; |
||||
|
std::vector<Public> m_freePeers; |
||||
|
std::map<Public, std::pair<bi::tcp::endpoint, unsigned>> m_incomingPeers; |
||||
|
|
||||
|
h256 m_latestBlockSent; |
||||
|
std::set<h256> m_transactionsSent; |
||||
|
|
||||
|
std::chrono::steady_clock::time_point m_lastPeersRequest; |
||||
|
unsigned m_idealPeerCount = 5; |
||||
|
|
||||
|
std::vector<bi::address_v4> m_addresses; |
||||
|
std::vector<bi::address_v4> m_peerAddresses; |
||||
|
|
||||
|
bool m_accepting = false; |
||||
|
}; |
||||
|
|
||||
|
} |
@ -0,0 +1,585 @@ |
|||||
|
/*
|
||||
|
This file is part of cpp-ethereum. |
||||
|
|
||||
|
cpp-ethereum is free software: you can redistribute it and/or modify |
||||
|
it under the terms of the GNU General Public License as published by |
||||
|
the Free Software Foundation, either version 3 of the License, or |
||||
|
(at your option) any later version. |
||||
|
|
||||
|
cpp-ethereum is distributed in the hope that it will be useful, |
||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
GNU General Public License for more details. |
||||
|
|
||||
|
You should have received a copy of the GNU General Public License |
||||
|
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
*/ |
||||
|
/** @file PeerSession.cpp
|
||||
|
* @author Gav Wood <i@gavwood.com> |
||||
|
* @date 2014 |
||||
|
*/ |
||||
|
|
||||
|
#include "PeerSession.h" |
||||
|
|
||||
|
#include <chrono> |
||||
|
#include "Exceptions.h" |
||||
|
#include "Common.h" |
||||
|
#include "BlockChain.h" |
||||
|
#include "BlockInfo.h" |
||||
|
#include "PeerServer.h" |
||||
|
using namespace std; |
||||
|
using namespace eth; |
||||
|
|
||||
|
#define clogS(X) eth::LogOutputStream<X, true>(false) << "| " << std::setw(2) << m_socket.native_handle() << "] " |
||||
|
|
||||
|
static const eth::uint c_maxHashes = 32; ///< Maximum number of hashes GetChain will ever send.
|
||||
|
static const eth::uint c_maxBlocks = 32; ///< Maximum number of blocks Blocks will ever send. BUG: if this gets too big (e.g. 2048) stuff starts going wrong.
|
||||
|
static const eth::uint c_maxBlocksAsk = 256; ///< Maximum number of blocks we ask to receive in Blocks (when using GetChain).
|
||||
|
|
||||
|
PeerSession::PeerSession(PeerServer* _s, bi::tcp::socket _socket, uint _rNId, bi::address _peerAddress, unsigned short _peerPort): |
||||
|
m_server(_s), |
||||
|
m_socket(std::move(_socket)), |
||||
|
m_reqNetworkId(_rNId), |
||||
|
m_listenPort(_peerPort), |
||||
|
m_rating(0) |
||||
|
{ |
||||
|
m_disconnect = std::chrono::steady_clock::time_point::max(); |
||||
|
m_connect = std::chrono::steady_clock::now(); |
||||
|
m_info = PeerInfo({"?", _peerAddress.to_string(), m_listenPort, std::chrono::steady_clock::duration(0)}); |
||||
|
} |
||||
|
|
||||
|
PeerSession::~PeerSession() |
||||
|
{ |
||||
|
m_socket.close(); |
||||
|
} |
||||
|
|
||||
|
bi::tcp::endpoint PeerSession::endpoint() const |
||||
|
{ |
||||
|
if (m_socket.is_open()) |
||||
|
try { |
||||
|
return bi::tcp::endpoint(m_socket.remote_endpoint().address(), m_listenPort); |
||||
|
} catch (...){} |
||||
|
|
||||
|
return bi::tcp::endpoint(); |
||||
|
} |
||||
|
|
||||
|
// TODO: BUG! 256 -> work out why things start to break with big packet sizes -> g.t. ~370 blocks.
|
||||
|
|
||||
|
bool PeerSession::interpret(RLP const& _r) |
||||
|
{ |
||||
|
clogS(NetRight) << _r; |
||||
|
switch (_r[0].toInt<unsigned>()) |
||||
|
{ |
||||
|
case HelloPacket: |
||||
|
{ |
||||
|
m_protocolVersion = _r[1].toInt<uint>(); |
||||
|
m_networkId = _r[2].toInt<uint>(); |
||||
|
auto clientVersion = _r[3].toString(); |
||||
|
m_caps = _r[4].toInt<uint>(); |
||||
|
m_listenPort = _r[5].toInt<unsigned short>(); |
||||
|
m_id = _r[6].toHash<h512>(); |
||||
|
|
||||
|
clogS(NetMessageSummary) << "Hello: " << clientVersion << "V[" << m_protocolVersion << "/" << m_networkId << "]" << m_id.abridged() << showbase << hex << m_caps << dec << m_listenPort; |
||||
|
|
||||
|
if (m_server->m_peers.count(m_id)) |
||||
|
if (auto l = m_server->m_peers[m_id].lock()) |
||||
|
if (l.get() != this && l->isOpen()) |
||||
|
{ |
||||
|
// Already connected.
|
||||
|
cwarn << "Already have peer id" << m_id.abridged() << "at" << l->endpoint() << "rather than" << endpoint(); |
||||
|
disconnect(DuplicatePeer); |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
if (m_protocolVersion != PeerServer::protocolVersion() || m_networkId != m_server->networkId() || !m_id) |
||||
|
{ |
||||
|
disconnect(IncompatibleProtocol); |
||||
|
return false; |
||||
|
} |
||||
|
try |
||||
|
{ m_info = PeerInfo({clientVersion, m_socket.remote_endpoint().address().to_string(), m_listenPort, std::chrono::steady_clock::duration()}); } |
||||
|
catch (...) |
||||
|
{ |
||||
|
disconnect(BadProtocol); |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
m_server->m_peers[m_id] = shared_from_this(); |
||||
|
|
||||
|
// Grab their block chain off them.
|
||||
|
{ |
||||
|
clogS(NetAllDetail) << "Want chain. Latest:" << m_server->m_latestBlockSent << ", number:" << m_server->m_chain->details(m_server->m_latestBlockSent).number; |
||||
|
uint count = std::min(c_maxHashes, m_server->m_chain->details(m_server->m_latestBlockSent).number + 1); |
||||
|
RLPStream s; |
||||
|
prep(s).appendList(2 + count); |
||||
|
s << GetChainPacket; |
||||
|
auto h = m_server->m_latestBlockSent; |
||||
|
for (uint i = 0; i < count; ++i, h = m_server->m_chain->details(h).parent) |
||||
|
{ |
||||
|
clogS(NetAllDetail) << " " << i << ":" << h; |
||||
|
s << h; |
||||
|
} |
||||
|
|
||||
|
s << c_maxBlocksAsk; |
||||
|
sealAndSend(s); |
||||
|
s.clear(); |
||||
|
prep(s).appendList(1); |
||||
|
s << GetTransactionsPacket; |
||||
|
sealAndSend(s); |
||||
|
} |
||||
|
break; |
||||
|
} |
||||
|
case DisconnectPacket: |
||||
|
{ |
||||
|
string reason = "Unspecified"; |
||||
|
if (_r[1].isInt()) |
||||
|
reason = reasonOf((DisconnectReason)_r[1].toInt<int>()); |
||||
|
|
||||
|
clogS(NetMessageSummary) << "Disconnect (reason: " << reason << ")"; |
||||
|
if (m_socket.is_open()) |
||||
|
clogS(NetNote) << "Closing " << m_socket.remote_endpoint(); |
||||
|
else |
||||
|
clogS(NetNote) << "Remote closed."; |
||||
|
m_socket.close(); |
||||
|
return false; |
||||
|
} |
||||
|
case PingPacket: |
||||
|
{ |
||||
|
// clogS(NetMessageSummary) << "Ping";
|
||||
|
RLPStream s; |
||||
|
sealAndSend(prep(s).appendList(1) << PongPacket); |
||||
|
break; |
||||
|
} |
||||
|
case PongPacket: |
||||
|
m_info.lastPing = std::chrono::steady_clock::now() - m_ping; |
||||
|
// clogS(NetMessageSummary) << "Latency: " << chrono::duration_cast<chrono::milliseconds>(m_lastPing).count() << " ms";
|
||||
|
break; |
||||
|
case GetPeersPacket: |
||||
|
{ |
||||
|
clogS(NetMessageSummary) << "GetPeers"; |
||||
|
auto peers = m_server->potentialPeers(); |
||||
|
RLPStream s; |
||||
|
prep(s).appendList(peers.size() + 1); |
||||
|
s << PeersPacket; |
||||
|
for (auto i: peers) |
||||
|
{ |
||||
|
clogS(NetMessageDetail) << "Sending peer " << toHex(i.first.ref().cropped(0, 4)) << i.second; |
||||
|
s.appendList(3) << i.second.address().to_v4().to_bytes() << i.second.port() << i.first; |
||||
|
} |
||||
|
sealAndSend(s); |
||||
|
break; |
||||
|
} |
||||
|
case PeersPacket: |
||||
|
clogS(NetMessageSummary) << "Peers (" << dec << (_r.itemCount() - 1) << " entries)"; |
||||
|
for (unsigned i = 1; i < _r.itemCount(); ++i) |
||||
|
{ |
||||
|
bi::address_v4 peerAddress(_r[i][0].toArray<byte, 4>()); |
||||
|
auto ep = bi::tcp::endpoint(peerAddress, _r[i][1].toInt<short>()); |
||||
|
Public id = _r[i][2].toHash<Public>(); |
||||
|
if (isPrivateAddress(peerAddress)) |
||||
|
goto CONTINUE; |
||||
|
|
||||
|
clogS(NetAllDetail) << "Checking: " << ep << "(" << toHex(id.ref().cropped(0, 4)) << ")"; |
||||
|
|
||||
|
// check that it's not us or one we already know:
|
||||
|
if (id && (m_server->m_key.pub() == id || m_server->m_peers.count(id) || m_server->m_incomingPeers.count(id))) |
||||
|
goto CONTINUE; |
||||
|
|
||||
|
// check that we're not already connected to addr:
|
||||
|
if (!ep.port()) |
||||
|
goto CONTINUE; |
||||
|
for (auto i: m_server->m_addresses) |
||||
|
if (ep.address() == i && ep.port() == m_server->listenPort()) |
||||
|
goto CONTINUE; |
||||
|
for (auto i: m_server->m_peers) |
||||
|
if (shared_ptr<PeerSession> p = i.second.lock()) |
||||
|
{ |
||||
|
clogS(NetAllDetail) << " ...against " << p->endpoint(); |
||||
|
if (p->m_socket.is_open() && p->endpoint() == ep) |
||||
|
goto CONTINUE; |
||||
|
} |
||||
|
for (auto i: m_server->m_incomingPeers) |
||||
|
if (i.second.first == ep) |
||||
|
goto CONTINUE; |
||||
|
m_server->m_incomingPeers[id] = make_pair(ep, 0); |
||||
|
m_server->m_freePeers.push_back(id); |
||||
|
clogS(NetMessageDetail) << "New peer: " << ep << "(" << id << ")"; |
||||
|
CONTINUE:; |
||||
|
} |
||||
|
break; |
||||
|
case TransactionsPacket: |
||||
|
if (m_server->m_mode == NodeMode::PeerServer) |
||||
|
break; |
||||
|
clogS(NetMessageSummary) << "Transactions (" << dec << (_r.itemCount() - 1) << " entries)"; |
||||
|
m_rating += _r.itemCount() - 1; |
||||
|
for (unsigned i = 1; i < _r.itemCount(); ++i) |
||||
|
{ |
||||
|
m_server->m_incomingTransactions.push_back(_r[i].data().toBytes()); |
||||
|
m_knownTransactions.insert(sha3(_r[i].data())); |
||||
|
} |
||||
|
break; |
||||
|
case BlocksPacket: |
||||
|
{ |
||||
|
if (m_server->m_mode == NodeMode::PeerServer) |
||||
|
break; |
||||
|
clogS(NetMessageSummary) << "Blocks (" << dec << (_r.itemCount() - 1) << " entries)"; |
||||
|
unsigned used = 0; |
||||
|
for (unsigned i = 1; i < _r.itemCount(); ++i) |
||||
|
{ |
||||
|
auto h = sha3(_r[i].data()); |
||||
|
if (!m_server->m_chain->details(h)) |
||||
|
{ |
||||
|
m_server->m_incomingBlocks.push_back(_r[i].data().toBytes()); |
||||
|
m_knownBlocks.insert(h); |
||||
|
used++; |
||||
|
} |
||||
|
} |
||||
|
m_rating += used; |
||||
|
if (g_logVerbosity >= 3) |
||||
|
for (unsigned i = 1; i < _r.itemCount(); ++i) |
||||
|
{ |
||||
|
auto h = sha3(_r[i].data()); |
||||
|
BlockInfo bi(_r[i].data()); |
||||
|
if (!m_server->m_chain->details(bi.parentHash) && !m_knownBlocks.count(bi.parentHash)) |
||||
|
clogS(NetMessageDetail) << "Unknown parent " << bi.parentHash << " of block " << h; |
||||
|
else |
||||
|
clogS(NetMessageDetail) << "Known parent " << bi.parentHash << " of block " << h; |
||||
|
} |
||||
|
if (used) // we received some - check if there's any more
|
||||
|
{ |
||||
|
RLPStream s; |
||||
|
prep(s).appendList(3); |
||||
|
s << GetChainPacket; |
||||
|
s << sha3(_r[1].data()); |
||||
|
s << c_maxBlocksAsk; |
||||
|
sealAndSend(s); |
||||
|
} |
||||
|
break; |
||||
|
} |
||||
|
case GetChainPacket: |
||||
|
{ |
||||
|
if (m_server->m_mode == NodeMode::PeerServer) |
||||
|
break; |
||||
|
clogS(NetMessageSummary) << "GetChain (" << (_r.itemCount() - 2) << " hashes, " << (_r[_r.itemCount() - 1].toInt<bigint>()) << ")"; |
||||
|
// ********************************************************************
|
||||
|
// NEEDS FULL REWRITE!
|
||||
|
h256s parents; |
||||
|
parents.reserve(_r.itemCount() - 2); |
||||
|
for (unsigned i = 1; i < _r.itemCount() - 1; ++i) |
||||
|
parents.push_back(_r[i].toHash<h256>()); |
||||
|
if (_r.itemCount() == 2) |
||||
|
break; |
||||
|
// return 2048 block max.
|
||||
|
uint baseCount = (uint)min<bigint>(_r[_r.itemCount() - 1].toInt<bigint>(), c_maxBlocks); |
||||
|
clogS(NetMessageSummary) << "GetChain (" << baseCount << " max, from " << parents.front() << " to " << parents.back() << ")"; |
||||
|
for (auto parent: parents) |
||||
|
{ |
||||
|
auto h = m_server->m_chain->currentHash(); |
||||
|
h256 latest = m_server->m_chain->currentHash(); |
||||
|
uint latestNumber = 0; |
||||
|
uint parentNumber = 0; |
||||
|
RLPStream s; |
||||
|
|
||||
|
if (m_server->m_chain->details(parent)) |
||||
|
{ |
||||
|
latestNumber = m_server->m_chain->details(latest).number; |
||||
|
parentNumber = m_server->m_chain->details(parent).number; |
||||
|
uint count = min<uint>(latestNumber - parentNumber, baseCount); |
||||
|
clogS(NetAllDetail) << "Requires " << dec << (latestNumber - parentNumber) << " blocks from " << latestNumber << " to " << parentNumber; |
||||
|
clogS(NetAllDetail) << latest << " - " << parent; |
||||
|
|
||||
|
prep(s); |
||||
|
s.appendList(1 + count) << BlocksPacket; |
||||
|
uint endNumber = m_server->m_chain->details(parent).number; |
||||
|
uint startNumber = endNumber + count; |
||||
|
clogS(NetAllDetail) << "Sending " << dec << count << " blocks from " << startNumber << " to " << endNumber; |
||||
|
|
||||
|
uint n = latestNumber; |
||||
|
for (; n > startNumber; n--, h = m_server->m_chain->details(h).parent) {} |
||||
|
for (uint i = 0; i < count; ++i, --n, h = m_server->m_chain->details(h).parent) |
||||
|
{ |
||||
|
if (h == parent || n == endNumber) |
||||
|
{ |
||||
|
cwarn << "BUG! Couldn't create the reply for GetChain!"; |
||||
|
return true; |
||||
|
} |
||||
|
clogS(NetAllDetail) << " " << dec << i << " " << h; |
||||
|
s.appendRaw(m_server->m_chain->block(h)); |
||||
|
} |
||||
|
clogS(NetAllDetail) << "Parent: " << h; |
||||
|
} |
||||
|
else if (parent != parents.back()) |
||||
|
continue; |
||||
|
|
||||
|
if (h != parent) |
||||
|
{ |
||||
|
// not in the blockchain;
|
||||
|
if (parent == parents.back()) |
||||
|
{ |
||||
|
// out of parents...
|
||||
|
clogS(NetAllDetail) << "GetChain failed; not in chain"; |
||||
|
// No good - must have been on a different branch.
|
||||
|
s.clear(); |
||||
|
prep(s).appendList(2) << NotInChainPacket << parents.back(); |
||||
|
} |
||||
|
else |
||||
|
// still some parents left - try them.
|
||||
|
continue; |
||||
|
} |
||||
|
// send the packet (either Blocks or NotInChain) & exit.
|
||||
|
sealAndSend(s); |
||||
|
break; |
||||
|
// ********************************************************************
|
||||
|
} |
||||
|
break; |
||||
|
} |
||||
|
case NotInChainPacket: |
||||
|
{ |
||||
|
if (m_server->m_mode == NodeMode::PeerServer) |
||||
|
break; |
||||
|
h256 noGood = _r[1].toHash<h256>(); |
||||
|
clogS(NetMessageSummary) << "NotInChain (" << noGood << ")"; |
||||
|
if (noGood == m_server->m_chain->genesisHash()) |
||||
|
{ |
||||
|
clogS(NetWarn) << "Discordance over genesis block! Disconnect."; |
||||
|
disconnect(WrongGenesis); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
uint count = std::min(c_maxHashes, m_server->m_chain->details(noGood).number); |
||||
|
RLPStream s; |
||||
|
prep(s).appendList(2 + count); |
||||
|
s << GetChainPacket; |
||||
|
auto h = m_server->m_chain->details(noGood).parent; |
||||
|
for (uint i = 0; i < count; ++i, h = m_server->m_chain->details(h).parent) |
||||
|
s << h; |
||||
|
s << c_maxBlocksAsk; |
||||
|
sealAndSend(s); |
||||
|
} |
||||
|
break; |
||||
|
} |
||||
|
case GetTransactionsPacket: |
||||
|
{ |
||||
|
if (m_server->m_mode == NodeMode::PeerServer) |
||||
|
break; |
||||
|
m_requireTransactions = true; |
||||
|
break; |
||||
|
} |
||||
|
default: |
||||
|
break; |
||||
|
} |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
void PeerSession::ping() |
||||
|
{ |
||||
|
RLPStream s; |
||||
|
sealAndSend(prep(s).appendList(1) << PingPacket); |
||||
|
m_ping = std::chrono::steady_clock::now(); |
||||
|
} |
||||
|
|
||||
|
RLPStream& PeerSession::prep(RLPStream& _s) |
||||
|
{ |
||||
|
return _s.appendRaw(bytes(8, 0)); |
||||
|
} |
||||
|
|
||||
|
void PeerServer::seal(bytes& _b) |
||||
|
{ |
||||
|
_b[0] = 0x22; |
||||
|
_b[1] = 0x40; |
||||
|
_b[2] = 0x08; |
||||
|
_b[3] = 0x91; |
||||
|
uint32_t len = (uint32_t)_b.size() - 8; |
||||
|
_b[4] = (len >> 24) & 0xff; |
||||
|
_b[5] = (len >> 16) & 0xff; |
||||
|
_b[6] = (len >> 8) & 0xff; |
||||
|
_b[7] = len & 0xff; |
||||
|
} |
||||
|
|
||||
|
void PeerSession::sealAndSend(RLPStream& _s) |
||||
|
{ |
||||
|
bytes b; |
||||
|
_s.swapOut(b); |
||||
|
m_server->seal(b); |
||||
|
sendDestroy(b); |
||||
|
} |
||||
|
|
||||
|
bool PeerSession::checkPacket(bytesConstRef _msg) |
||||
|
{ |
||||
|
if (_msg.size() < 8) |
||||
|
return false; |
||||
|
if (!(_msg[0] == 0x22 && _msg[1] == 0x40 && _msg[2] == 0x08 && _msg[3] == 0x91)) |
||||
|
return false; |
||||
|
uint32_t len = ((_msg[4] * 256 + _msg[5]) * 256 + _msg[6]) * 256 + _msg[7]; |
||||
|
if (_msg.size() != len + 8) |
||||
|
return false; |
||||
|
RLP r(_msg.cropped(8)); |
||||
|
if (r.actualSize() != len) |
||||
|
return false; |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
void PeerSession::sendDestroy(bytes& _msg) |
||||
|
{ |
||||
|
clogS(NetLeft) << RLP(bytesConstRef(&_msg).cropped(8)); |
||||
|
|
||||
|
if (!checkPacket(bytesConstRef(&_msg))) |
||||
|
{ |
||||
|
cwarn << "INVALID PACKET CONSTRUCTED!"; |
||||
|
} |
||||
|
|
||||
|
auto self(shared_from_this()); |
||||
|
bytes* buffer = new bytes(std::move(_msg)); |
||||
|
ba::async_write(m_socket, ba::buffer(*buffer), [self, buffer](boost::system::error_code ec, std::size_t length) |
||||
|
{ |
||||
|
delete buffer; |
||||
|
if (ec) |
||||
|
{ |
||||
|
cwarn << "Error sending: " << ec.message(); |
||||
|
self->dropped(); |
||||
|
} |
||||
|
// cbug << length << " bytes written (EC: " << ec << ")";
|
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
void PeerSession::send(bytesConstRef _msg) |
||||
|
{ |
||||
|
clogS(NetLeft) << RLP(_msg.cropped(8)); |
||||
|
|
||||
|
if (!checkPacket(_msg)) |
||||
|
{ |
||||
|
cwarn << "INVALID PACKET CONSTRUCTED!"; |
||||
|
} |
||||
|
|
||||
|
auto self(shared_from_this()); |
||||
|
bytes* buffer = new bytes(_msg.toBytes()); |
||||
|
ba::async_write(m_socket, ba::buffer(*buffer), [self, buffer](boost::system::error_code ec, std::size_t length) |
||||
|
{ |
||||
|
delete buffer; |
||||
|
if (ec) |
||||
|
{ |
||||
|
cwarn << "Error sending: " << ec.message(); |
||||
|
self->dropped(); |
||||
|
} |
||||
|
// cbug << length << " bytes written (EC: " << ec << ")";
|
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
void PeerSession::dropped() |
||||
|
{ |
||||
|
if (m_socket.is_open()) |
||||
|
try { |
||||
|
clogS(NetNote) << "Closing " << m_socket.remote_endpoint(); |
||||
|
m_socket.close(); |
||||
|
}catch (...){} |
||||
|
for (auto i = m_server->m_peers.begin(); i != m_server->m_peers.end(); ++i) |
||||
|
if (i->second.lock().get() == this) |
||||
|
{ |
||||
|
m_server->m_peers.erase(i); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void PeerSession::disconnect(int _reason) |
||||
|
{ |
||||
|
clogS(NetNote) << "Disconnecting (reason:" << reasonOf((DisconnectReason)_reason) << ")"; |
||||
|
if (m_socket.is_open()) |
||||
|
{ |
||||
|
if (m_disconnect == chrono::steady_clock::time_point::max()) |
||||
|
{ |
||||
|
RLPStream s; |
||||
|
prep(s); |
||||
|
s.appendList(2) << DisconnectPacket << _reason; |
||||
|
sealAndSend(s); |
||||
|
m_disconnect = chrono::steady_clock::now(); |
||||
|
} |
||||
|
else |
||||
|
dropped(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void PeerSession::start() |
||||
|
{ |
||||
|
RLPStream s; |
||||
|
prep(s); |
||||
|
s.appendList(7) << HelloPacket << (uint)PeerServer::protocolVersion() << m_server->networkId() << m_server->m_clientVersion << (m_server->m_mode == NodeMode::Full ? 0x07 : m_server->m_mode == NodeMode::PeerServer ? 0x01 : 0) << m_server->m_public.port() << m_server->m_key.pub(); |
||||
|
sealAndSend(s); |
||||
|
|
||||
|
ping(); |
||||
|
|
||||
|
doRead(); |
||||
|
} |
||||
|
|
||||
|
void PeerSession::doRead() |
||||
|
{ |
||||
|
auto self(shared_from_this()); |
||||
|
m_socket.async_read_some(boost::asio::buffer(m_data), [this,self](boost::system::error_code ec, std::size_t length) |
||||
|
{ |
||||
|
if (ec) |
||||
|
{ |
||||
|
cwarn << "Error reading: " << ec.message(); |
||||
|
dropped(); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
m_incoming.resize(m_incoming.size() + length); |
||||
|
memcpy(m_incoming.data() + m_incoming.size() - length, m_data.data(), length); |
||||
|
while (m_incoming.size() > 8) |
||||
|
{ |
||||
|
if (m_incoming[0] != 0x22 || m_incoming[1] != 0x40 || m_incoming[2] != 0x08 || m_incoming[3] != 0x91) |
||||
|
{ |
||||
|
clogS(NetWarn) << "Out of alignment."; |
||||
|
disconnect(BadProtocol); |
||||
|
return; |
||||
|
clogS(NetNote) << "Skipping: " << hex << showbase << (int)m_incoming[0] << dec; |
||||
|
memmove(m_incoming.data(), m_incoming.data() + 1, m_incoming.size() - 1); |
||||
|
m_incoming.resize(m_incoming.size() - 1); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
uint32_t len = fromBigEndian<uint32_t>(bytesConstRef(m_incoming.data() + 4, 4)); |
||||
|
uint32_t tlen = len + 8; |
||||
|
if (m_incoming.size() < tlen) |
||||
|
break; |
||||
|
|
||||
|
// enough has come in.
|
||||
|
// cerr << "Received " << len << ": " << toHex(bytesConstRef(m_incoming.data() + 8, len)) << endl;
|
||||
|
auto data = bytesConstRef(m_incoming.data(), tlen); |
||||
|
if (!checkPacket(data)) |
||||
|
{ |
||||
|
cerr << "Received " << len << ": " << toHex(bytesConstRef(m_incoming.data() + 8, len)) << endl; |
||||
|
cwarn << "INVALID MESSAGE RECEIVED"; |
||||
|
disconnect(BadProtocol); |
||||
|
return; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
RLP r(data.cropped(8)); |
||||
|
if (!interpret(r)) |
||||
|
{ |
||||
|
// error
|
||||
|
dropped(); |
||||
|
return; |
||||
|
} |
||||
|
} |
||||
|
memmove(m_incoming.data(), m_incoming.data() + tlen, m_incoming.size() - tlen); |
||||
|
m_incoming.resize(m_incoming.size() - tlen); |
||||
|
} |
||||
|
} |
||||
|
doRead(); |
||||
|
} |
||||
|
catch (Exception const& _e) |
||||
|
{ |
||||
|
clogS(NetWarn) << "ERROR: " << _e.description(); |
||||
|
dropped(); |
||||
|
} |
||||
|
catch (std::exception const& _e) |
||||
|
{ |
||||
|
clogS(NetWarn) << "ERROR: " << _e.what(); |
||||
|
dropped(); |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
} |
@ -0,0 +1,90 @@ |
|||||
|
/*
|
||||
|
This file is part of cpp-ethereum. |
||||
|
|
||||
|
cpp-ethereum is free software: you can redistribute it and/or modify |
||||
|
it under the terms of the GNU General Public License as published by |
||||
|
the Free Software Foundation, either version 3 of the License, or |
||||
|
(at your option) any later version. |
||||
|
|
||||
|
cpp-ethereum is distributed in the hope that it will be useful, |
||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
GNU General Public License for more details. |
||||
|
|
||||
|
You should have received a copy of the GNU General Public License |
||||
|
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
*/ |
||||
|
/** @file PeerSession.h
|
||||
|
* @author Gav Wood <i@gavwood.com> |
||||
|
* @date 2014 |
||||
|
*/ |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <array> |
||||
|
#include <set> |
||||
|
#include <memory> |
||||
|
#include <utility> |
||||
|
#include "RLP.h" |
||||
|
#include "CommonEth.h" |
||||
|
#include "PeerNetwork.h" |
||||
|
|
||||
|
namespace eth |
||||
|
{ |
||||
|
|
||||
|
class PeerSession: public std::enable_shared_from_this<PeerSession> |
||||
|
{ |
||||
|
friend class PeerServer; |
||||
|
|
||||
|
public: |
||||
|
PeerSession(PeerServer* _server, bi::tcp::socket _socket, uint _rNId, bi::address _peerAddress, unsigned short _peerPort = 0); |
||||
|
~PeerSession(); |
||||
|
|
||||
|
void start(); |
||||
|
void disconnect(int _reason); |
||||
|
|
||||
|
void ping(); |
||||
|
|
||||
|
bool isOpen() const { return m_socket.is_open(); } |
||||
|
|
||||
|
bi::tcp::endpoint endpoint() const; ///< for other peers to connect to.
|
||||
|
|
||||
|
private: |
||||
|
void dropped(); |
||||
|
void doRead(); |
||||
|
void doWrite(std::size_t length); |
||||
|
bool interpret(RLP const& _r); |
||||
|
|
||||
|
/// @returns true iff the _msg forms a valid message for sending or receiving on the network.
|
||||
|
static bool checkPacket(bytesConstRef _msg); |
||||
|
|
||||
|
static RLPStream& prep(RLPStream& _s); |
||||
|
void sealAndSend(RLPStream& _s); |
||||
|
void sendDestroy(bytes& _msg); |
||||
|
void send(bytesConstRef _msg); |
||||
|
PeerServer* m_server; |
||||
|
|
||||
|
bi::tcp::socket m_socket; |
||||
|
std::array<byte, 65536> m_data; |
||||
|
PeerInfo m_info; |
||||
|
Public m_id; |
||||
|
|
||||
|
bytes m_incoming; |
||||
|
uint m_protocolVersion; |
||||
|
uint m_networkId; |
||||
|
uint m_reqNetworkId; |
||||
|
unsigned short m_listenPort; ///< Port that the remote client is listening on for connections. Useful for giving to peers.
|
||||
|
uint m_caps; |
||||
|
|
||||
|
std::chrono::steady_clock::time_point m_ping; |
||||
|
std::chrono::steady_clock::time_point m_connect; |
||||
|
std::chrono::steady_clock::time_point m_disconnect; |
||||
|
|
||||
|
uint m_rating; |
||||
|
bool m_requireTransactions; |
||||
|
|
||||
|
std::set<h256> m_knownBlocks; |
||||
|
std::set<h256> m_knownTransactions; |
||||
|
}; |
||||
|
|
||||
|
} |
@ -1,8 +0,0 @@ |
|||||
#!/bin/bash |
|
||||
|
|
||||
set -e |
|
||||
rm -f ../cpp-ethereum_*_source.changes |
|
||||
debuild -S -sa |
|
||||
cd .. |
|
||||
dput -f ppa:ethereum/ethereum cpp-ethereum_*_source.changes |
|
||||
|
|
@ -1,65 +0,0 @@ |
|||||
#!/bin/bash |
|
||||
|
|
||||
dist="saucy" |
|
||||
version=$(grep "define ETH_VERSION" libethereum/Common.h | cut -d ' ' -f 3) |
|
||||
branch="$(git branch | grep \* | cut -c 3-)" |
|
||||
|
|
||||
if [[ ! "$1" == "" ]]; then |
|
||||
version=$1 |
|
||||
fi |
|
||||
|
|
||||
if [[ ! "$3" == "" ]]; then |
|
||||
if [[ ! "$4" == "" ]]; then |
|
||||
dist=$4 |
|
||||
fi |
|
||||
if [[ "$2" == "-i" ]]; then |
|
||||
# increment current debian release only |
|
||||
# new version ./release VERSION -i MESSAGE DIST |
|
||||
debchange -i -p "$3" -D "$dist" |
|
||||
git commit -a -m "$3" |
|
||||
else |
|
||||
# new version ./release VERSION DEB-VERSION MESSAGE DIST |
|
||||
debchange -v $version-$2 -p "$3" -D "$dist" |
|
||||
git commit -a -m "$3" |
|
||||
fi |
|
||||
fi |
|
||||
|
|
||||
opwd=`pwd` |
|
||||
cd /tmp |
|
||||
|
|
||||
echo Checking out... |
|
||||
git clone $opwd |
|
||||
cd cpp-ethereum |
|
||||
git checkout "$branch" |
|
||||
|
|
||||
archdir="cpp-ethereum-$version" |
|
||||
archfile="$archdir.tar.bz2" |
|
||||
|
|
||||
echo Cleaning backup files... |
|
||||
find . | grep \~ | xargs rm -f |
|
||||
|
|
||||
echo Cleaning others... |
|
||||
rm release.sh |
|
||||
|
|
||||
echo Cleaning versioning... |
|
||||
rm -rf .git .gitignore |
|
||||
|
|
||||
echo Renaming directory... |
|
||||
cd .. |
|
||||
rm -rf $archdir |
|
||||
mv cpp-ethereum $archdir |
|
||||
|
|
||||
echo Creating archive... |
|
||||
tar c $archdir | bzip2 -- > $archfile |
|
||||
|
|
||||
[[ ! "$version" == "" ]] && ln -sf $archfile "cpp-ethereum_$version.orig.tar.bz2" |
|
||||
|
|
||||
echo Packaging... |
|
||||
cd "$archdir" |
|
||||
./package.sh |
|
||||
|
|
||||
echo Cleaning up... |
|
||||
rm -rf /tmp/$archdir |
|
||||
mv /tmp/$archfile ~ |
|
||||
|
|
||||
echo Done. |
|
@ -0,0 +1,25 @@ |
|||||
|
|
||||
|
function os.capture(cmd) |
||||
|
local f = io.popen(cmd, 'r') |
||||
|
if (f) then |
||||
|
local s = f:read('*a') |
||||
|
if (f:close()) then |
||||
|
return s |
||||
|
end |
||||
|
end |
||||
|
return nil |
||||
|
end |
||||
|
|
||||
|
hash = (os.capture("git rev-parse HEAD") or "UnknownRevision"):gsub("\n$", "") |
||||
|
clean = ((os.capture("git diff --name-only") or "0"):gsub("\n$", "") == "") and "1" or "0" |
||||
|
|
||||
|
local output = io.open(arg[1], "w") |
||||
|
if (output) then |
||||
|
output:write("// This file was automatically generated by buildinfo.lua\n#pragma once\n\n") |
||||
|
output:write("#define ETH_COMMIT_HASH "..hash.."\n") |
||||
|
output:write("#define ETH_CLEAN_REPO "..clean.."\n") |
||||
|
output:close() |
||||
|
end |
||||
|
|
||||
|
|
||||
|
|
@ -0,0 +1,16 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
||||
|
<ItemGroup> |
||||
|
<ClCompile Include="..\eth\main.cpp" /> |
||||
|
</ItemGroup> |
||||
|
<ItemGroup> |
||||
|
<Filter Include="Windows"> |
||||
|
<UniqueIdentifier>{ed0eafbf-bbfb-4700-b7c0-9b58049cc681}</UniqueIdentifier> |
||||
|
</Filter> |
||||
|
</ItemGroup> |
||||
|
<ItemGroup> |
||||
|
<CustomBuild Include="BuildInfo.lua"> |
||||
|
<Filter>Windows</Filter> |
||||
|
</CustomBuild> |
||||
|
</ItemGroup> |
||||
|
</Project> |
Loading…
Reference in new issue