Files

513 lines
16 KiB
C++

/*
* This file is part of the Flowee project
* Copyright (C) 2017-2026 Tom Zander <tom@flowee.org>
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Tx.h"
#include "Block.h"
#include "TxIterator_p.h"
#include "transaction.h"
#include <streaming/streams.h>
#include <compat/endian.h>
#include <hash.h>
#include <cstring>
Tx::Tx()
{
}
Tx::Tx(const Streaming::ConstBuffer &rawTransaction)
: m_data(rawTransaction)
{
}
uint32_t Tx::txVersion() const
{
return static_cast<uint32_t>(le32toh(*((uint32_t*)m_data.begin())));
}
uint256 Tx::createHash() const
{
CHash256 ctx;
ctx.write((const unsigned char*) m_data.begin(), m_data.size());
uint256 result;
ctx.finalize((unsigned char*)&result);
return result;
}
CTransaction Tx::createOldTransaction() const
{
CTransaction answer;
CDataStream buf(m_data.begin(), m_data.end(), 0 , 0);
answer.Unserialize(buf, 0, 0);
return answer;
}
int64_t Tx::offsetInBlock(const Block &block) const
{
assert(m_data.isValid());
assert(block.data().isValid());
assert(block.data().internal_buffer() == m_data.internal_buffer());
return m_data.begin() - block.data().begin();
}
Tx Tx::fromOldTransaction(const CTransaction &transaction, const std::shared_ptr<Streaming::BufferPool> &pool)
{
CSizeComputer sc(0, 0);
sc << transaction;
if (pool) {
pool->reserve(sc.size());
transaction.Serialize(*pool, 0, 0);
return Tx(pool->commit());
}
Streaming::BufferPool pl(sc.size());
transaction.Serialize(pl, 0, 0);
return Tx(pl.commit());
}
// static
std::list<Tx::Input> Tx::findInputs(Tx::Iterator &iter)
{
std::list<Input> inputs;
Input curInput;
auto content = iter.next();
while (content != Tx::End) {
if (content == Tx::PrevTxHash) { // only read inputs for non-coinbase txs
if (iter.byteData().size() != 32)
throw std::runtime_error("Failed to understand PrevTxHash");
curInput.txid = iter.uint256Data();
content = iter.next(Tx::PrevTxIndex);
if (content != Tx::PrevTxIndex)
throw std::runtime_error("Failed to find PrevTxIndex");
curInput.index = iter.intData();
inputs.push_back(curInput);
}
else if (content == Tx::OutputValue) {
break;
}
content = iter.next();
}
return inputs;
}
// static
Tx::Output Tx::nextOutput(Tx::Iterator &iter)
{
return iter.nextOutput();
}
Tx::Output Tx::output(int index) const
{
assert(index >= 0);
Iterator iter(*this);
return iter.nextOutput(index);
}
////////////////////////////////////////////////////////////
bool static isConstBytes(Tx::Component tag)
{
return tag == Tx::TxVersion || tag == Tx::LockTime || tag == Tx::PrevTxIndex || tag == Tx::Sequence;
}
TxTokenizer::TxTokenizer(const Streaming::ConstBuffer &buffer)
: m_data(buffer),
m_txStart(m_data.begin()),
m_currentTokenStart(m_txStart),
m_currentTokenEnd(m_txStart),
m_tag(Tx::Unset)
{
}
TxTokenizer::TxTokenizer(const Block &block, int offsetInBlock)
: m_data(block.data()),
m_txStart(nullptr),
m_tag(Tx::Unset)
{
assert(block.isFullBlock());
const char *pos;
if (offsetInBlock == 0) {
pos = m_data.begin() + 80;
pos += readCompactSizeSize(pos); // num tx field
} else {
pos = m_data.begin() + offsetInBlock;
}
m_currentTokenStart = pos;
m_currentTokenEnd = pos;
}
Tx::Component TxTokenizer::next() {
if (m_currentTokenEnd + 1 >= m_data.end()) {
m_tag = Tx::End;
return tag();
}
m_currentTokenStart = m_currentTokenEnd;
if (m_tag == Tx::Unset || m_tag == Tx::End) {
m_numInputsLeft = -1;
m_numOutputsLeft = -1;
m_txStart = m_currentTokenStart;
m_currentTokenEnd += 4;
m_tag = Tx::TxVersion;
return checkSpaceForTag();
}
if (m_tag == Tx::TxVersion) {
uint64_t x = readCompactSize(&m_currentTokenEnd, m_data.end());
if (x > 0xFFFF)
throw std::runtime_error("Tx invalid");
m_numInputsLeft = static_cast<int>(x);
// we immediately go to the next token
m_currentTokenStart = m_currentTokenEnd;
if (0 == m_numInputsLeft)
m_tag = Tx::Sequence; // last tag of an input. So we jump to output processing
}
if (m_numInputsLeft > 0) {
if (m_tag == Tx::TxVersion || m_tag == Tx::Sequence) {
m_isCashToken = false;
m_cashTokenHasCommitment = false;
m_cashTokenHasAmount = false;
m_currentTokenEnd += 32;
m_tag = Tx::PrevTxHash;
return checkSpaceForTag();
}
if (m_tag == Tx::PrevTxHash) {
m_currentTokenEnd += 4;
m_tag = Tx::PrevTxIndex;
return checkSpaceForTag();
}
if (m_tag == Tx::PrevTxIndex) {
uint64_t scriptLength = readCompactSize(&m_currentTokenEnd, m_data.end());
if (scriptLength > 0xFFFF)
throw std::runtime_error("Tx invalid");
m_currentTokenStart = m_currentTokenEnd;
m_currentTokenEnd += static_cast<int>(scriptLength);
m_tag = Tx::TxInScript;
return checkSpaceForTag();
}
if (m_tag == Tx::TxInScript) {
m_numInputsLeft--;
m_currentTokenEnd += 4;
m_tag = Tx::Sequence;
return checkSpaceForTag();
}
}
if (m_tag == Tx::Sequence) {
uint64_t x = readCompactSize(&m_currentTokenEnd, m_data.end());
if (x > 0xFFFF)
throw std::runtime_error("Tx invalid");
m_numOutputsLeft = static_cast<int>(x);
// we immediately go to the next token
m_currentTokenStart = m_currentTokenEnd;
if (m_numOutputsLeft == 0) // jump over outputs
m_tag = Tx::OutputScript;
}
if (m_numOutputsLeft > 0) {
if (m_tag == Tx::Sequence || m_tag == Tx::OutputScript) {
m_currentTokenEnd += 8;
m_outputScriptStart = nullptr;
m_tag = Tx::OutputValue;
return checkSpaceForTag();
}
if (m_tag == Tx::OutputValue) {
m_scriptTokenLength = readCompactSize(&m_currentTokenEnd, m_data.end());
if (m_scriptTokenLength > 0xFFFF)
throw std::runtime_error("Tx invalid");
m_currentTokenStart = m_currentTokenEnd;
m_outputScriptStart = m_currentTokenStart;
m_outputScriptEnd = m_currentTokenStart + m_scriptTokenLength;
unsigned char tokenPrefix = static_cast<unsigned char>(*m_currentTokenStart);
m_isCashToken = (tokenPrefix == 0xef);
if (m_isCashToken) {
m_scriptTokenLength--;
// we immediately go to the next token
m_currentTokenEnd++;
m_currentTokenStart = m_currentTokenEnd;
}
}
if (m_isCashToken) {
if (m_tag == Tx::OutputValue) {
m_scriptTokenLength -= 32;
m_currentTokenEnd += 32;
m_tag = Tx::CashTokenCategory;
return checkSpaceForTag();
}
if (m_tag == Tx::CashTokenCategory) {
// Read the token_bitfield
m_scriptTokenLength--;
unsigned char bitfield = static_cast<unsigned char>(*m_currentTokenStart);
m_cashTokenHasCommitment = (bitfield & Tx::HasCommitment);
m_cashTokenHasAmount = (bitfield & Tx::HasFtAmount);
m_currentTokenEnd += 1;
m_tag = Tx::CashTokenBitfield;
return checkSpaceForTag();
}
if (m_cashTokenHasCommitment && m_tag == Tx::CashTokenBitfield) {
// Has a cash token amount (setting in bitfield), and previous tag was CashTokenBitfield
uint64_t commitmentLength = readCompactSize(&m_currentTokenEnd, m_data.end());
if (commitmentLength > 0xFFFF)
throw std::runtime_error("Commitment invalid");
m_scriptTokenLength -= (m_currentTokenEnd - m_currentTokenStart);
m_currentTokenStart = m_currentTokenEnd;
m_currentTokenEnd += static_cast<int>(commitmentLength);
m_scriptTokenLength -= commitmentLength;
m_tag = Tx::CashTokenCommitment;
return checkSpaceForTag();
}
if (m_cashTokenHasAmount &&
(m_tag == Tx::CashTokenCommitment || (m_tag == Tx::CashTokenBitfield && !m_cashTokenHasCommitment))) {
// Has a cash token amount (setting in bitfield), and, either:
// 1) Previous tag was CashTokenCommitment
// 2) Previous tag was CashTokenBitfield + it did not have a commitment
m_currentTokenEnd += readCompactSizeSize(m_currentTokenStart);
m_scriptTokenLength -= (m_currentTokenEnd - m_currentTokenStart);
m_tag = Tx::CashTokenAmount;
return checkSpaceForTag();
}
}
{ // no if-statement, at this position only the OutputScript is allowed
m_numOutputsLeft--;
m_currentTokenStart = m_currentTokenEnd;
m_currentTokenEnd += static_cast<int>(m_scriptTokenLength);
m_tag = Tx::OutputScript;
m_isCashToken = false;
return checkSpaceForTag();
}
}
if (m_tag == Tx::OutputScript) {
m_currentTokenEnd += 4;
m_tag = Tx::LockTime;
return checkSpaceForTag();
}
if (m_tag == Tx::LockTime)
m_tag = Tx::End;
else
assert(false);
return tag();
}
Tx::Component TxTokenizer::checkSpaceForTag()
{
if (m_tag != Tx::End && m_currentTokenEnd > m_data.end())
throw std::runtime_error("Tx data missing");
return tag();
}
Tx::Iterator::Iterator(const Tx &tx)
: d(new TxTokenizer(tx.m_data))
{
}
Tx::Iterator::Iterator(const Block &block, int offsetInBlock)
: d(new TxTokenizer(block, offsetInBlock))
{
}
Tx::Iterator::Iterator(const Tx::Iterator && other)
{
d = std::move(other.d);
}
Tx::Iterator::~Iterator()
{
delete d;
}
Tx::Component Tx::Iterator::next(int filter)
{
do {
int tag = d->next();
if (filter == 0 || (tag & filter))
return static_cast<Tx::Component>(tag);
} while (d->tag() != Tx::End);
return Tx::End;
}
Tx::Component Tx::Iterator::tag() const
{
return d->tag();
}
Tx Tx::Iterator::prevTx() const
{
assert(d->tag() == Tx::End);
return Tx(Streaming::ConstBuffer(d->m_data.internal_buffer(), d->m_txStart, d->m_currentTokenEnd));
}
Streaming::ConstBuffer Tx::Iterator::byteData() const
{
return Streaming::ConstBuffer(d->m_data.internal_buffer(), d->m_currentTokenStart, d->m_currentTokenEnd);
}
Streaming::ConstBuffer Tx::Iterator::rawOutputScript() const
{
if (d->m_outputScriptEnd <= d->m_outputScriptStart || d->m_outputScriptEnd > d->m_data.end())
return Streaming::ConstBuffer();
return Streaming::ConstBuffer(d->m_data.internal_buffer(), d->m_outputScriptStart, d->m_outputScriptEnd);
}
int Tx::Iterator::dataLength() const
{
return d->m_currentTokenEnd - d->m_currentTokenStart;
}
int32_t Tx::Iterator::intData() const
{
if (isConstBytes(d->tag()))
return *((int32_t*)(d->m_currentTokenStart));
const char *tmp = d->m_currentTokenStart;
return readCompactSize(&tmp, d->m_data.end());
}
uint32_t Tx::Iterator::uintData() const
{
if (isConstBytes(d->tag()))
return le32toh(*((uint32_t*)(d->m_currentTokenStart)));
const char *tmp = d->m_currentTokenStart;
return readCompactSize(&tmp, d->m_data.end());
}
uint64_t Tx::Iterator::longData() const
{
if (d->tag() == OutputValue)
return le64toh(*((uint64_t*)(d->m_currentTokenStart)));
if (isConstBytes(d->tag()))
return le32toh(*((uint32_t*)(d->m_currentTokenStart)));
const char *tmp = d->m_currentTokenStart;
return readCompactSize(&tmp, d->m_data.end());
}
uint256 Tx::Iterator::uint256Data() const
{
assert (d->m_currentTokenEnd - d->m_currentTokenStart >= 32);
uint256 answer(d->m_currentTokenStart);
return answer;
}
void Tx::Iterator::hashByteData(uint256 &output) const
{
CSHA256 hasher;
hasher.write(d->m_currentTokenStart, dataLength());
uint256 buf;
hasher.finalize(output.begin());
}
uint8_t Tx::Iterator::bitfieldData() const
{
if (d->m_tag != CashTokenBitfield)
return 0;
return *d->m_currentTokenStart;
}
size_t Tx::Iterator::position() const
{
return d->m_currentTokenStart - d->m_txStart;
}
Tx::Output Tx::Iterator::nextOutput(int skip)
{
Output answer;
auto content = tag();
if (tag() != Tx::OutputValue)
content = next(Tx::OutputValue);
while (content != Tx::End) {
if (--skip < 0) { // use this one!
answer.outputValue = static_cast<int64_t>(longData());
content = next();
size_t start1 = position();
if (content == Tx::CashTokenCategory) {
--start1; // since it auto-jumps over the marker.
content = next(Tx::OutputScript); // for 'offset()'
}
if (content != Tx::OutputScript)
throw std::runtime_error("Malformed transaction");
answer.m_rawOutputScript = rawOutputScript();
answer.m_outputStart = position() - start1;
assert(answer.m_outputStart >= 0);
break;
}
content = next(Tx::OutputValue);
}
return answer;
}
bool operator==(const Tx::Input &a, const Tx::Input &b)
{
return a.index == b.index && a.dataFile == b.dataFile && a.txid == b.txid;
}
// ------
Tx::Output::Output(const Streaming::ConstBuffer &b, int64_t v)
: m_rawOutputScript(b),
outputValue(v),
m_outputStart(0)
{
// pre-calc m_outputStart if there is a token.
if (hasToken()) {
Tx::Token token;
const char *t = m_rawOutputScript.begin();
assert(static_cast<uint8_t>(*t) == 0xef);
token.bitfield = static_cast<uint8_t>(t[33]);
t += 34;
if (token.hasCommitment())
t += readCompactSize(&t, m_rawOutputScript.end());
if (token.hasAmount())
(void) readCompactSize(&t, m_rawOutputScript.end());
m_outputStart = t - m_rawOutputScript.begin();
}
}
Streaming::ConstBuffer Tx::Output::outputScript() const
{
assert(m_outputStart > -1);
return m_rawOutputScript.mid(m_outputStart);
}
Streaming::ConstBuffer Tx::Output::rawOutputScript() const
{
return m_rawOutputScript;
}
bool Tx::Output::hasToken() const
{
if (m_rawOutputScript.size() < 34)
return false;
return static_cast<uint8_t>(*m_rawOutputScript.begin()) == 0xef;
}
Tx::Token Tx::Output::token() const
{
if (!hasToken())
throw std::runtime_error("No token");
Tx::Token answer;
const char *t = m_rawOutputScript.begin();
assert(static_cast<uint8_t>(*t) == 0xef);
answer.category = uint256(t + 1);
answer.bitfield = static_cast<uint8_t>(t[33]);
t += 34;
if (answer.hasCommitment()) {
auto length = readCompactSize(&t, m_rawOutputScript.end());
answer.commitment = std::vector<uint8_t>(t, t + length);
t += length;
}
if (answer.hasAmount())
answer.amount = readCompactSize(&t, m_rawOutputScript.end());
return answer;
}