From 02f40093d08394f1bf3c8dfeced77a6ad593f935 Mon Sep 17 00:00:00 2001 From: Pavel Baykov Date: Tue, 7 Jun 2022 09:02:56 -0300 Subject: [PATCH] ethereum transaction class and RLP --- .../peerplays_sidechain/ethereum/RLP.h | 472 ++++++++++++++++++ .../ethereum/transaction.hpp | 78 +++ 2 files changed, 550 insertions(+) create mode 100644 libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/ethereum/RLP.h create mode 100644 libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/ethereum/transaction.hpp diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/ethereum/RLP.h b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/ethereum/RLP.h new file mode 100644 index 00000000..99a77023 --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/ethereum/RLP.h @@ -0,0 +1,472 @@ +// Aleth: Ethereum C++ client, tools and libraries. +// Copyright 2013-2019 Aleth Authors. +// Licensed under the GNU General Public License, Version 3. + +/// @file +/// Recursive Linear-Prefix serialization / deserialization. +#pragma once + +#include "Exceptions.h" +#include "FixedHash.h" +#include "vector_ref.h" + +#include +#include +#include +#include +#include + +namespace dev +{ + +class RLP; + +template struct intTraits { static const unsigned maxSize = sizeof(_T); }; +template <> struct intTraits { static const unsigned maxSize = 20; }; +template <> struct intTraits { static const unsigned maxSize = 32; }; +template <> struct intTraits { static const unsigned maxSize = ~(unsigned)0; }; + +static const byte c_rlpMaxLengthBytes = 8; +static const byte c_rlpDataImmLenStart = 0x80; +static const byte c_rlpListStart = 0xc0; + +static const byte c_rlpDataImmLenCount = c_rlpListStart - c_rlpDataImmLenStart - c_rlpMaxLengthBytes; +static const byte c_rlpDataIndLenZero = c_rlpDataImmLenStart + c_rlpDataImmLenCount - 1; +static const byte c_rlpListImmLenCount = 256 - c_rlpListStart - c_rlpMaxLengthBytes; +static const byte c_rlpListIndLenZero = c_rlpListStart + c_rlpListImmLenCount - 1; + +template struct Converter { static T convert(RLP const&, int) { BOOST_THROW_EXCEPTION(BadCast()); } }; + +/** + * Class for interpreting Recursive Linear-Prefix Data. + */ +class RLP +{ +public: + /// Conversion flags + enum + { + AllowNonCanon = 1, + ThrowOnFail = 4, + FailIfTooBig = 8, + FailIfTooSmall = 16, + Strict = ThrowOnFail | FailIfTooBig, + VeryStrict = ThrowOnFail | FailIfTooBig | FailIfTooSmall, + LaissezFaire = AllowNonCanon + }; + + using Strictness = int; + + /// Construct a null node. + RLP() {} + + /// Construct a node of value given in the bytes. + explicit RLP(bytesConstRef _d, Strictness _s = VeryStrict); + + /// Construct a node of value given in the bytes. + explicit RLP(bytes const& _d, Strictness _s = VeryStrict): RLP(&_d, _s) {} + + /// Construct a node to read RLP data in the bytes given. + RLP(byte const* _b, unsigned _s, Strictness _st = VeryStrict): RLP(bytesConstRef(_b, _s), _st) {} + + /// Construct a node to read RLP data in the string. + explicit RLP(std::string const& _s, Strictness _st = VeryStrict): RLP(bytesConstRef((byte const*)_s.data(), _s.size()), _st) {} + + /// The bare data of the RLP. + bytesConstRef data() const { return m_data; } + + /// @returns true if the RLP is non-null. + explicit operator bool() const { return !isNull(); } + + /// No value. + bool isNull() const { return m_data.size() == 0; } + + /// Contains a zero-length string or zero-length list. + bool isEmpty() const { return !isNull() && (m_data[0] == c_rlpDataImmLenStart || m_data[0] == c_rlpListStart); } + + /// String value. + bool isData() const { return !isNull() && m_data[0] < c_rlpListStart; } + + /// List value. + bool isList() const { return !isNull() && m_data[0] >= c_rlpListStart; } + + /// Integer value. Must not have a leading zero. + bool isInt() const; + + /// @returns the number of items in the list, or zero if it isn't a list. + size_t itemCount() const { return isList() ? items() : 0; } + size_t itemCountStrict() const { if (!isList()) BOOST_THROW_EXCEPTION(BadCast()); return items(); } + + /// @returns the number of bytes in the data, or zero if it isn't data. + size_t size() const { return isData() ? length() : 0; } + size_t sizeStrict() const { if (!isData()) BOOST_THROW_EXCEPTION(BadCast()); return length(); } + + /// Equality operators; does best-effort conversion and checks for equality. + bool operator==(char const* _s) const { return isData() && toString() == _s; } + bool operator!=(char const* _s) const { return isData() && toString() != _s; } + bool operator==(std::string const& _s) const { return isData() && toString() == _s; } + bool operator!=(std::string const& _s) const { return isData() && toString() != _s; } + template bool operator==(FixedHash<_N> const& _h) const { return isData() && toHash<_N>() == _h; } + template bool operator!=(FixedHash<_N> const& _s) const { return isData() && toHash<_N>() != _s; } + bool operator==(unsigned const& _i) const { return isInt() && toInt() == _i; } + bool operator!=(unsigned const& _i) const { return isInt() && toInt() != _i; } + bool operator==(u256 const& _i) const { return isInt() && toInt() == _i; } + bool operator!=(u256 const& _i) const { return isInt() && toInt() != _i; } + bool operator==(bigint const& _i) const { return isInt() && toInt() == _i; } + bool operator!=(bigint const& _i) const { return isInt() && toInt() != _i; } + + /// Subscript operator. + /// @returns the list item @a _i if isList() and @a _i < listItems(), or RLP() otherwise. + /// @note if used to access items in ascending order, this is efficient. + RLP operator[](size_t _i) const; + + using element_type = RLP; + + /// @brief Iterator class for iterating through items of RLP list. + class iterator + { + friend class RLP; + + public: + using value_type = RLP; + using element_type = RLP; + + iterator& operator++(); + iterator operator++(int) { auto ret = *this; operator++(); return ret; } + RLP operator*() const { return RLP(m_currentItem); } + bool operator==(iterator const& _cmp) const { return m_currentItem == _cmp.m_currentItem; } + bool operator!=(iterator const& _cmp) const { return !operator==(_cmp); } + + private: + iterator() {} + iterator(RLP const& _parent, bool _begin); + + size_t m_remaining = 0; + bytesConstRef m_currentItem; + }; + + /// @brief Iterator into beginning of sub-item list (valid only if we are a list). + iterator begin() const { return iterator(*this, true); } + + /// @brief Iterator into end of sub-item list (valid only if we are a list). + iterator end() const { return iterator(*this, false); } + + template inline T convert(int _flags) const; + + /// Best-effort conversion operators. + explicit operator std::string() const { return toString(); } + explicit operator bytes() const { return toBytes(); } + explicit operator uint8_t() const { return toInt(); } + explicit operator uint16_t() const { return toInt(); } + explicit operator uint32_t() const { return toInt(); } + explicit operator uint64_t() const { return toInt(); } + explicit operator u160() const { return toInt(); } + explicit operator u256() const { return toInt(); } + explicit operator bigint() const { return toInt(); } + template explicit operator FixedHash() const { return toHash>(); } + template explicit operator std::pair() const { return toPair(); } + template explicit operator std::vector() const { return toVector(); } + template explicit operator std::set() const { return toSet(); } + template explicit operator std::array() const { return toArray(); } + + /// Converts to bytearray. @returns the empty byte array if not a string. + bytes toBytes(int _flags = LaissezFaire) const { if (!isData()) { if (_flags & ThrowOnFail) BOOST_THROW_EXCEPTION(BadCast()); else return bytes(); } return bytes(payload().data(), payload().data() + length()); } + /// Converts to bytearray. @returns the empty byte array if not a string. + bytesConstRef toBytesConstRef(int _flags = LaissezFaire) const { if (!isData()) { if (_flags & ThrowOnFail) BOOST_THROW_EXCEPTION(BadCast()); else return bytesConstRef(); } return payload().cropped(0, length()); } + /// Converts to string. @returns the empty string if not a string. + std::string toString(int _flags = LaissezFaire) const { if (!isData()) { if (_flags & ThrowOnFail) BOOST_THROW_EXCEPTION(BadCast()); else return std::string(); } return payload().cropped(0, length()).toString(); } + /// Converts to string. @throws BadCast if not a string. + std::string toStringStrict() const { return toString(Strict); } + + template + std::vector toVector(int _flags = LaissezFaire) const + { + std::vector ret; + if (isList()) + { + ret.reserve(itemCount()); + for (auto const& i: *this) + ret.push_back(i.convert(_flags)); + } + else if (_flags & ThrowOnFail) + BOOST_THROW_EXCEPTION(BadCast()); + return ret; + } + + template + std::set toSet(int _flags = LaissezFaire) const + { + std::set ret; + if (isList()) + for (auto const& i: *this) + ret.insert(i.convert(_flags)); + else if (_flags & ThrowOnFail) + BOOST_THROW_EXCEPTION(BadCast()); + return ret; + } + + template + std::unordered_set toUnorderedSet(int _flags = LaissezFaire) const + { + std::unordered_set ret; + if (isList()) + for (auto const& i: *this) + ret.insert(i.convert(_flags)); + else if (_flags & ThrowOnFail) + BOOST_THROW_EXCEPTION(BadCast()); + return ret; + } + + template + std::pair toPair(int _flags = Strict) const + { + std::pair ret; + if (itemCountStrict() != 2) + { + if (_flags & ThrowOnFail) + BOOST_THROW_EXCEPTION(BadCast()); + else + return ret; + } + ret.first = (*this)[0].convert(_flags); + ret.second = (*this)[1].convert(_flags); + return ret; + } + + template + std::array toArray(int _flags = LaissezFaire) const + { + if (itemCount() != N) + { + if (_flags & ThrowOnFail) + BOOST_THROW_EXCEPTION(BadCast()); + else + return std::array(); + } + std::array ret; + for (size_t i = 0; i < N; ++i) + ret[i] = operator[](i).convert(_flags); + return ret; + } + + /// Converts to int of type given; if isData(), decodes as big-endian bytestream. @returns 0 if not an int or data. + template _T toInt(int _flags = Strict) const + { + requireGood(); + if ((!isInt() && !(_flags & AllowNonCanon)) || isList() || isNull()) + { + if (_flags & ThrowOnFail) + BOOST_THROW_EXCEPTION(BadCast()); + else + return 0; + } + + auto p = payload(); + if (p.size() > intTraits<_T>::maxSize && (_flags & FailIfTooBig)) + { + if (_flags & ThrowOnFail) + BOOST_THROW_EXCEPTION(BadCast()); + else + return 0; + } + + return fromBigEndian<_T>(p); + } + + int64_t toPositiveInt64(int _flags = Strict) const + { + int64_t i = toInt(_flags); + if ((_flags & ThrowOnFail) && i < 0) + BOOST_THROW_EXCEPTION(BadCast()); + return i; + } + + template _N toHash(int _flags = Strict) const + { + requireGood(); + auto p = payload(); + auto l = p.size(); + if (!isData() || (l > _N::size && (_flags & FailIfTooBig)) || (l < _N::size && (_flags & FailIfTooSmall))) + { + if (_flags & ThrowOnFail) + BOOST_THROW_EXCEPTION(BadCast()); + else + return _N(); + } + + _N ret; + size_t s = std::min(_N::size, l); + memcpy(ret.data() + _N::size - s, p.data(), s); + return ret; + } + + /// @returns the data payload. Valid for all types. + bytesConstRef payload() const { auto l = length(); if (l > m_data.size()) BOOST_THROW_EXCEPTION(BadRLP()); return m_data.cropped(payloadOffset(), l); } + + /// @returns the theoretical size of this item as encoded in the data. + /// @note Under normal circumstances, is equivalent to m_data.size() - use that unless you know it won't work. + size_t actualSize() const; + +private: + /// Disable construction from rvalue + explicit RLP(bytes const&&) {} + + /// Throws if is non-canonical data (i.e. single byte done in two bytes that could be done in one). + void requireGood() const; + + /// Single-byte data payload. + bool isSingleByte() const { return !isNull() && m_data[0] < c_rlpDataImmLenStart; } + + /// @returns the amount of bytes used to encode the length of the data. Valid for all types. + unsigned lengthSize() const { if (isData() && m_data[0] > c_rlpDataIndLenZero) return m_data[0] - c_rlpDataIndLenZero; if (isList() && m_data[0] > c_rlpListIndLenZero) return m_data[0] - c_rlpListIndLenZero; return 0; } + + /// @returns the size in bytes of the payload, as given by the RLP as opposed to as inferred from m_data. + size_t length() const; + + /// @returns the number of bytes into the data that the payload starts. + size_t payloadOffset() const { return isSingleByte() ? 0 : (1 + lengthSize()); } + + /// @returns the number of data items. + size_t items() const; + + /// @returns the size encoded into the RLP in @a _data and throws if _data is too short. + static size_t sizeAsEncoded(bytesConstRef _data) { return RLP(_data, ThrowOnFail | FailIfTooSmall).actualSize(); } + + /// Our byte data. + bytesConstRef m_data; + + /// The list-indexing cache. + // Index of the last item accessed with operator[] + mutable size_t m_lastIndex = (size_t)-1; + // Offset of the next byte after last byte of m_lastItem + mutable size_t m_lastEnd = 0; + // Data of the last item accessed with operator[] + mutable bytesConstRef m_lastItem; +}; + +template <> struct Converter { static std::string convert(RLP const& _r, int _flags) { return _r.toString(_flags); } }; +template <> struct Converter { static bytes convert(RLP const& _r, int _flags) { return _r.toBytes(_flags); } }; +template <> struct Converter { static uint8_t convert(RLP const& _r, int _flags) { return _r.toInt(_flags); } }; +template <> struct Converter { static uint16_t convert(RLP const& _r, int _flags) { return _r.toInt(_flags); } }; +template <> struct Converter { static uint32_t convert(RLP const& _r, int _flags) { return _r.toInt(_flags); } }; +template <> struct Converter { static uint64_t convert(RLP const& _r, int _flags) { return _r.toInt(_flags); } }; +template <> struct Converter { static u160 convert(RLP const& _r, int _flags) { return _r.toInt(_flags); } }; +template <> struct Converter { static u256 convert(RLP const& _r, int _flags) { return _r.toInt(_flags); } }; +template <> struct Converter { static bigint convert(RLP const& _r, int _flags) { return _r.toInt(_flags); } }; +template struct Converter> { static FixedHash convert(RLP const& _r, int _flags) { return _r.toHash>(_flags); } }; +template struct Converter> { static std::pair convert(RLP const& _r, int _flags) { return _r.toPair(_flags); } }; +template struct Converter> { static std::vector convert(RLP const& _r, int _flags) { return _r.toVector(_flags); } }; +template struct Converter> { static std::set convert(RLP const& _r, int _flags) { return _r.toSet(_flags); } }; +template struct Converter> { static std::unordered_set convert(RLP const& _r, int _flags) { return _r.toUnorderedSet(_flags); } }; +template struct Converter> { static std::array convert(RLP const& _r, int _flags) { return _r.toArray(_flags); } }; + +template inline T RLP::convert(int _flags) const { return Converter::convert(*this, _flags); } + +/** + * @brief Class for writing to an RLP bytestream. + */ +class RLPStream +{ +public: + /// Initializes empty RLPStream. + RLPStream() {} + + /// Initializes the RLPStream as a list of @a _listItems items. + explicit RLPStream(size_t _listItems) { appendList(_listItems); } + + ~RLPStream() {} + + /// Append given datum to the byte stream. + RLPStream& append(unsigned _s) { return append(bigint(_s)); } + RLPStream& append(u160 _s) { return append(bigint(_s)); } + RLPStream& append(u256 _s) { return append(bigint(_s)); } + RLPStream& append(bigint _s); + RLPStream& append(bytesConstRef _s, bool _compact = false); + RLPStream& append(bytes const& _s) { return append(bytesConstRef(&_s)); } + RLPStream& append(std::string const& _s) { return append(bytesConstRef(_s)); } + RLPStream& append(char const* _s) { return append(std::string(_s)); } + template RLPStream& append(FixedHash _s, bool _compact = false, bool _allOrNothing = false) { return _allOrNothing && !_s ? append(bytesConstRef()) : append(_s.ref(), _compact); } + + /// Appends an arbitrary RLP fragment - this *must* be a single item unless @a _itemCount is given. + RLPStream& append(RLP const& _rlp, size_t _itemCount = 1) { return appendRaw(_rlp.data(), _itemCount); } + + /// Appends a sequence of data to the stream as a list. + template RLPStream& append(std::vector<_T> const& _s) { return appendVector(_s); } + template RLPStream& appendVector(std::vector<_T> const& _s) { appendList(_s.size()); for (auto const& i: _s) append(i); return *this; } + template RLPStream& append(std::array<_T, S> const& _s) { appendList(_s.size()); for (auto const& i: _s) append(i); return *this; } + template RLPStream& append(std::set<_T> const& _s) { appendList(_s.size()); for (auto const& i: _s) append(i); return *this; } + template RLPStream& append(std::unordered_set<_T> const& _s) { appendList(_s.size()); for (auto const& i: _s) append(i); return *this; } + template RLPStream& append(std::pair const& _s) { appendList(2); append(_s.first); append(_s.second); return *this; } + + /// Appends a list. + RLPStream& appendList(size_t _items); + RLPStream& appendList(bytesConstRef _rlp); + RLPStream& appendList(bytes const& _rlp) { return appendList(&_rlp); } + RLPStream& appendList(RLPStream const& _s) { return appendList(&_s.out()); } + + /// Appends raw (pre-serialised) RLP data. Use with caution. + RLPStream& appendRaw(bytesConstRef _rlp, size_t _itemCount = 1); + RLPStream& appendRaw(bytes const& _rlp, size_t _itemCount = 1) { return appendRaw(&_rlp, _itemCount); } + + /// Shift operators for appending data items. + template RLPStream& operator<<(T _data) { return append(_data); } + + /// Clear the output stream so far. + void clear() { m_out.clear(); m_listStack.clear(); } + + /// Read the byte stream. + bytes const& out() const { if(!m_listStack.empty()) BOOST_THROW_EXCEPTION(RLPException() << errinfo_comment("listStack is not empty")); return m_out; } + + /// Invalidate the object and steal the output byte stream. + bytes&& invalidate() { if(!m_listStack.empty()) BOOST_THROW_EXCEPTION(RLPException() << errinfo_comment("listStack is not empty")); return std::move(m_out); } + + /// Swap the contents of the output stream out for some other byte array. + void swapOut(bytes& _dest) { if(!m_listStack.empty()) BOOST_THROW_EXCEPTION(RLPException() << errinfo_comment("listStack is not empty")); swap(m_out, _dest); } + +private: + void noteAppended(size_t _itemCount = 1); + + /// Push the node-type byte (using @a _base) along with the item count @a _count. + /// @arg _count is number of characters for strings, data-bytes for ints, or items for lists. + void pushCount(size_t _count, byte _offset); + + /// Push an integer as a raw big-endian byte-stream. + template void pushInt(_T _i, size_t _br) + { + m_out.resize(m_out.size() + _br); + byte* b = &m_out.back(); + for (; _i; _i >>= 8) + *(b--) = (byte)(_i & 0xff); + } + + /// Our output byte stream. + bytes m_out; + + std::vector> m_listStack; +}; + +template void rlpListAux(RLPStream& _out, _T _t) { _out << _t; } +template void rlpListAux(RLPStream& _out, _T _t, _Ts ... _ts) { rlpListAux(_out << _t, _ts...); } + +/// Export a single item in RLP format, returning a byte array. +template bytes rlp(_T _t) { return (RLPStream() << _t).out(); } + +/// Export a list of items in RLP format, returning a byte array. +inline bytes rlpList() { return RLPStream(0).out(); } +template bytes rlpList(_Ts ... _ts) +{ + RLPStream out(sizeof ...(_Ts)); + rlpListAux(out, _ts...); + return out.out(); +} + +/// The empty string in RLP format. +extern bytes RLPNull; + +/// The empty list in RLP format. +extern bytes RLPEmptyList; + +/// Human readable version of RLP. +std::ostream& operator<<(std::ostream& _out, dev::RLP const& _d); + +} diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/ethereum/transaction.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/ethereum/transaction.hpp new file mode 100644 index 00000000..116a6df3 --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/ethereum/transaction.hpp @@ -0,0 +1,78 @@ +#pragma once + +#include +#include + +#include +#include + + +namespace graphene { namespace peerplays_sidechain { namespace ethereum { + +class transaction { + uint16_t ref_block_num = 0; + uint32_t ref_block_prefix = 0; + fc::time_point_sec expiration; + std::vector operations; + extensions_type extensions; + + digest_type digest() const; + transaction_id_type id() const; + digest_type sig_digest(const chain_id_type &chain_id) const; + + void set_expiration(fc::time_point_sec expiration_time); + void set_reference_block(const block_id_type &reference_block); + + /// Serialises this transaction to an RLPStream. + /// @throws TransactionIsUnsigned if including signature was requested but it was not initialized + void streamRLP(RLPStream& _s, IncludeSignature _sig = WithSignature, bool _forEip155hash = false) const; + + /// @returns the RLP serialisation of this transaction. + bytes rlp(IncludeSignature _sig = WithSignature) const { RLPStream s; streamRLP(s, _sig); return s.out(); } +protected: + /// Type of transaction. + enum Type + { + NullTransaction, ///< Null transaction. + ContractCreation, ///< Transaction to create contracts - receiveAddress() is ignored. + MessageCall ///< Transaction to invoke a message call - receiveAddress() is used. + }; + + static bool isZeroSignature(u256 const& _r, u256 const& _s) { return !_r && !_s; } + + /// Clears the signature. + void clearSignature() { m_vrs = SignatureStruct(); } + + Type m_type = NullTransaction; ///< Is this a contract-creation transaction or a message-call transaction? + u256 m_nonce; ///< The transaction-count of the sender. + u256 m_value; ///< The amount of ETH to be transferred by this transaction. Called 'endowment' for contract-creation transactions. + Address m_receiveAddress; ///< The receiving address of the transaction. + u256 m_gasPrice; ///< The base fee and thus the implied exchange rate of ETH to GAS. + u256 m_gas; ///< The total gas to convert, paid for from sender's account. Any unused gas gets refunded once the contract is ended. + bytes m_data; ///< The data associated with the transaction, or the initialiser if it's a creation transaction. + boost::optional m_vrs; ///< The signature of the transaction. Encodes the sender. + /// EIP155 value for calculating transaction hash + /// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md + boost::optional m_chainId; + + mutable h256 m_hashWith; ///< Cached hash of transaction with signature. + mutable boost::optional
m_sender; ///< Cached sender, determined from signature. +}; + +class signed_transaction : public transaction { +public: + std::vector signatures; + + const signature_type &sign(const ethereum::private_key_type &key, const ethereum::chain_id_type &chain_id); + signature_type sign(const ethereum::private_key_type &key, const ethereum::chain_id_type &chain_id) const; + void clear(); +}; + +}}} // namespace graphene::peerplays_sidechain::ethereum + +FC_REFLECT(graphene::peerplays_sidechain::ethereum::transaction, + (ref_block_num)(ref_block_prefix)(expiration)(operations)(extensions)) + +FC_REFLECT_DERIVED(graphene::peerplays_sidechain::ethereum::signed_transaction, + (graphene::peerplays_sidechain::ethereum::transaction), + (signatures))