Skip to content

Commit c08c6c5

Browse files
authored
Merge pull request #578 from evoskuil/master
Optimize transaction writes.
2 parents 711262c + bcdb602 commit c08c6c5

14 files changed

Lines changed: 205 additions & 152 deletions

File tree

include/bitcoin/database/error.hpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,12 +117,11 @@ enum error_t : uint8_t
117117
tx_empty,
118118
tx_tx_allocate,
119119
tx_input_put,
120-
tx_point_allocate,
121-
tx_point_put,
122-
tx_ins_allocate,
123120
tx_ins_put,
124121
tx_output_put,
125122
tx_outs_put,
123+
tx_point_allocate,
124+
tx_point_put,
126125
tx_tx_set,
127126
tx_address_allocate,
128127
tx_address_put,

include/bitcoin/database/impl/primitives/arraymap.ipp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,12 @@ Link CLASS::count() const NOEXCEPT
112112
return body_.count();
113113
}
114114

115+
TEMPLATE
116+
bool CLASS::expand(const Link& count) NOEXCEPT
117+
{
118+
return body_.expand(count);
119+
}
120+
115121
// query interface
116122
// ----------------------------------------------------------------------------
117123

include/bitcoin/database/impl/primitives/hashmap.ipp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,12 @@ Link CLASS::count() const NOEXCEPT
104104
return body_.count();
105105
}
106106

107+
TEMPLATE
108+
bool CLASS::expand(const Link& count) NOEXCEPT
109+
{
110+
return body_.expand(count);
111+
}
112+
107113
// query interface
108114
// ----------------------------------------------------------------------------
109115

include/bitcoin/database/impl/query/archive_write.ipp

Lines changed: 43 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -89,97 +89,42 @@ code CLASS::set_code(tx_link& tx_fk, const transaction& tx) NOEXCEPT
8989
if (tx.is_empty())
9090
return error::tx_empty;
9191

92+
using ix = linkage<schema::index>;
9293
const auto& ins = tx.inputs_ptr();
9394
const auto& ous = tx.outputs_ptr();
94-
95-
using ix = linkage<schema::index>;
9695
const auto txs = possible_narrow_cast<tx_link::integer>(one);
9796
const auto inputs = possible_narrow_cast<ix::integer>(ins->size());
9897
const auto outputs = possible_narrow_cast<ix::integer>(ous->size());
9998

100-
// Declare outs record for output accumulation.
101-
table::outs::record outs{};
102-
outs.out_fks.reserve(outputs);
10399

104100
// ========================================================================
105101
const auto scope = store_.get_transactor();
106102

107-
// Allocate single tx record.
103+
// Allocate tx record.
108104
tx_fk = store_.tx.allocate(txs);
109105
if (tx_fk.is_terminal())
110106
return error::tx_tx_allocate;
111107

112-
// Allocate points records.
113-
const auto point_fk = store_.point.allocate(inputs);
114-
auto point_it = point_fk;
115-
if (point_fk.is_terminal())
116-
return error::tx_point_allocate;
117-
118-
// Expand synchronizes keys with point allocation.
119-
if (!store_.ins.expand(point_fk + inputs))
120-
return error::tx_ins_allocate;
108+
// Allocate contiguously and store inputs (script & witness).
109+
input_link in_fk{};
110+
if (!store_.input.put_link(in_fk, table::input::put_ref{ {}, tx }))
111+
return error::tx_input_put;
121112

122-
// Commit input and ins|point records.
123-
for (const auto& in: *ins)
124-
{
125-
// TODO: preallocate (requires input sizes).
126-
input_link input_fk{};
127-
if (!store_.input.put_link(input_fk, table::input::put_ref
128-
{
129-
{},
130-
*in
131-
}))
132-
{
133-
return error::tx_input_put;
134-
}
113+
// Allocate contiguously and store outputs (script, w/parent).
114+
output_link out_fk{};
115+
if (!store_.output.put_link(out_fk, table::output::put_ref{ {}, tx_fk, tx }))
116+
return error::tx_output_put;
135117

136-
if (!store_.ins.put(point_it++, table::ins::record
137-
{
138-
{},
139-
in->sequence(),
140-
input_fk,
141-
tx_fk
142-
}))
143-
{
144-
return error::tx_ins_put;
145-
}
146-
}
118+
// Allocate and contiguously store input links (first + tx.input sizes, w/parent & seq).
119+
point_link ins_fk{};
120+
if (!store_.ins.put_link(ins_fk, table::ins::put_ref{ {}, in_fk, tx_fk, tx }))
121+
return error::tx_ins_put;
147122

148-
// TODO: preallocate (requires output sizes).
149-
// Commit output records.
150-
for (const auto& out: *ous)
151-
{
152-
output_link output_fk{};
153-
if (!store_.output.put_link(output_fk, table::output::put_ref
154-
{
155-
{},
156-
tx_fk,
157-
*out
158-
}))
159-
{
160-
return error::tx_output_put;
161-
}
162-
163-
// Accumulate outputs in order.
164-
outs.out_fks.push_back(output_fk);
165-
}
166-
167-
// Commit accumulated outs.
168-
const auto outs_fk = store_.outs.put_link(outs);
169-
if (outs_fk.is_terminal())
123+
// Allocate and contiguously store output links (first + tx.output sizes).
124+
point_link outs_fk{};
125+
if (!store_.outs.put_link(outs_fk, table::outs::put_ref{ {}, out_fk, tx }))
170126
return error::tx_outs_put;
171127

172-
// Commit accumulated points.
173-
point_it = point_fk;
174-
for (const auto& in: *ins)
175-
{
176-
const auto key = table::point::compose(in->point());
177-
if (!store_.point.put(point_it++, key, table::point::record{}))
178-
{
179-
return error::tx_point_put;
180-
}
181-
}
182-
183128
// Create tx record.
184129
// Commit is deferred for point/address index consistency.
185130
if (!store_.tx.set(tx_fk, table::transaction::record_put_ref
@@ -188,38 +133,50 @@ code CLASS::set_code(tx_link& tx_fk, const transaction& tx) NOEXCEPT
188133
tx,
189134
inputs,
190135
outputs,
191-
point_fk,
136+
ins_fk,
192137
outs_fk
193138
}))
194139
{
195140
return error::tx_tx_set;
196141
}
197142

198-
// Commit address index records.
143+
// Commit points (hashmap).
144+
{
145+
// Expand synchronizes keys with ins_fk, entries dropped into same offset.
146+
// Allocate contiguous points (at sequential keys matching ins_fk).
147+
if (!store_.point.expand(ins_fk + inputs))
148+
return error::tx_point_allocate;
149+
150+
// This must be set after tx.set and before tx.commit, since searchable and
151+
// produces an association to tx.link, and is also an integral part of tx.
152+
const auto ptr = store_.point.get_memory();
153+
for (const auto& in: *ins)
154+
{
155+
if (!store_.point.put(ptr, ins_fk++,
156+
table::point::compose(in->point()), table::point::record{}))
157+
return error::tx_point_put;
158+
}
159+
}
160+
161+
// Commit address index records (hashmap).
199162
if (address_enabled())
200163
{
201164
auto ad_fk = store_.address.allocate(outputs);
202165
if (ad_fk.is_terminal())
203166
return error::tx_address_allocate;
204167

205-
auto output = ous->begin();
206168
const auto ptr = store_.address.get_memory();
207-
208-
for (auto out_fk: outs.out_fks)
169+
for (const auto& output: *ous)
209170
{
210-
const auto key = (*output++)->script().hash();
211-
if (!store_.address.put(ptr, ad_fk++, key, table::address::record
212-
{
213-
{},
214-
out_fk
215-
}))
216-
{
171+
if (!store_.address.put(ptr, ad_fk++, output->script().hash(),
172+
table::address::record{ {}, out_fk }))
217173
return error::tx_address_put;
218-
}
174+
175+
out_fk.value += output->serialized_size();
219176
}
220177
}
221178

222-
// Commit tx to search.
179+
// Commit tx to search (hashmap).
223180
// tx.get_hash() assumes cached or is not thread safe.
224181
return store_.tx.commit(tx_fk, tx.get_hash(false)) ?
225182
error::success : error::tx_tx_commit;

include/bitcoin/database/primitives/arraymap.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@ class arraymap
7373
/// Count of records (or body file bytes if slab).
7474
Link count() const NOEXCEPT;
7575

76+
/// Increase count as neccesary to specified.
77+
bool expand(const Link& count) NOEXCEPT;
78+
7679
/// Errors.
7780
/// -----------------------------------------------------------------------
7881

include/bitcoin/database/primitives/hashmap.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ class hashmap
7474
/// Count of records (or body file bytes if slab).
7575
Link count() const NOEXCEPT;
7676

77+
/// Increase count as neccesary to specified.
78+
bool expand(const Link& count) NOEXCEPT;
79+
7780
/// Errors.
7881
/// -----------------------------------------------------------------------
7982

include/bitcoin/database/tables/archives/input.hpp

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -101,20 +101,37 @@ struct input
101101
{
102102
inline link count() const NOEXCEPT
103103
{
104-
return system::possible_narrow_cast<link::integer>(
105-
input.script().serialized_size(true) +
106-
input.witness().serialized_size(true));
104+
using namespace system;
105+
static constexpr auto sequence_point_size = sizeof(uint32_t) +
106+
chain::point::serialized_size();
107+
108+
const auto& ins = *tx_.inputs_ptr();
109+
const auto other = ins.size() * sequence_point_size;
110+
const auto inputs = std::accumulate(ins.begin(), ins.end(), zero,
111+
[](size_t total, const auto& in) NOEXCEPT
112+
{
113+
// sizes cached, so this is free.
114+
return total + in->serialized_size(true);
115+
});
116+
117+
// (script + witness + sequence + point) - (sequence + point)
118+
return possible_narrow_cast<link::integer>(inputs - other);
107119
}
108120

109121
inline bool to_data(flipper& sink) const NOEXCEPT
110122
{
111-
input.script().to_data(sink, true);
112-
input.witness().to_data(sink, true);
123+
const auto& ins = *tx_.inputs_ptr();
124+
std::for_each(ins.begin(), ins.end(), [&](const auto& in) NOEXCEPT
125+
{
126+
in->script().to_data(sink, true);
127+
in->witness().to_data(sink, true);
128+
});
129+
113130
BC_ASSERT(!sink || sink.get_write_position() == count());
114131
return sink;
115132
}
116133

117-
const system::chain::input& input{};
134+
const system::chain::transaction& tx_{};
118135
};
119136
};
120137

include/bitcoin/database/tables/archives/ins.hpp

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,42 @@ struct ins
9797
uint32_t sequence{};
9898
in::integer input_fk{};
9999
};
100+
101+
struct put_ref
102+
: public schema::ins
103+
{
104+
link count() const NOEXCEPT
105+
{
106+
return system::possible_narrow_cast<link::integer>(tx_.inputs());
107+
}
108+
109+
inline bool to_data(flipper& sink) const NOEXCEPT
110+
{
111+
using namespace system;
112+
static constexpr auto sequence_point_size = sizeof(uint32_t) +
113+
chain::point::serialized_size();
114+
115+
auto in_fk = input_fk;
116+
const auto& ins = *tx_.inputs_ptr();
117+
std::for_each(ins.begin(), ins.end(), [&](const auto& in) NOEXCEPT
118+
{
119+
sink.write_little_endian<uint32_t>(in->sequence());
120+
sink.write_little_endian<in::integer, in::size>(in_fk);
121+
sink.write_little_endian<tx::integer, tx::size>(parent_fk);
122+
123+
// Calculate next corresponding input fk from serialized size.
124+
// (script + witness + sequence + point) - (sequence + point)
125+
in_fk += (in->serialized_size(true) - sequence_point_size);
126+
});
127+
128+
BC_ASSERT(!sink || sink.get_write_position() == count() * minrow);
129+
return sink;
130+
}
131+
132+
const in::integer input_fk{};
133+
const tx::integer parent_fk{};
134+
const system::chain::transaction& tx_{};
135+
};
100136
};
101137

102138
} // namespace table

include/bitcoin/database/tables/archives/output.hpp

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -150,23 +150,42 @@ struct output
150150
{
151151
inline link count() const NOEXCEPT
152152
{
153-
return system::possible_narrow_cast<link::integer>(
154-
tx::size +
155-
variable_size(output.value()) +
156-
output.script().serialized_size(true));
153+
using namespace system;
154+
static_assert(tx::size <= sizeof(uint64_t));
155+
static constexpr auto value_parent_difference = sizeof(uint64_t) -
156+
tx::size;
157+
158+
const auto& outs = *tx_.outputs_ptr();
159+
const auto other = outs.size() * value_parent_difference;
160+
const auto outputs = std::accumulate(outs.begin(), outs.end(), zero,
161+
[](size_t total, const auto& out) NOEXCEPT
162+
{
163+
// size cached, so this is free, includes sizeof(value).
164+
return total + variable_size(out->value()) +
165+
out->serialized_size();
166+
});
167+
168+
// Converts value from fixed size wire encoding to variable.
169+
// (variable_size(value) + (value + script)) - (value - parent)
170+
return possible_narrow_cast<link::integer>(outputs - other);
157171
}
158172

159173
inline bool to_data(flipper& sink) const NOEXCEPT
160174
{
161-
sink.write_little_endian<tx::integer, tx::size>(parent_fk);
162-
sink.write_variable(output.value());
163-
output.script().to_data(sink, true);
175+
const auto& outs = *tx_.outputs_ptr();
176+
std::for_each(outs.begin(), outs.end(), [&](const auto& out) NOEXCEPT
177+
{
178+
sink.write_little_endian<tx::integer, tx::size>(parent_fk);
179+
sink.write_variable(out->value());
180+
out->script().to_data(sink, true);
181+
});
182+
164183
BC_ASSERT(!sink || sink.get_write_position() == count());
165184
return sink;
166185
}
167186

168-
tx::integer parent_fk{};
169-
const system::chain::output& output{};
187+
const tx::integer parent_fk{};
188+
const system::chain::transaction& tx_{};
170189
};
171190
};
172191

0 commit comments

Comments
 (0)