Skip to content

Commit

Permalink
🚧 init mempool logic
Browse files Browse the repository at this point in the history
  • Loading branch information
AbdelStark committed Sep 3, 2024
1 parent a86cac6 commit 8798e20
Show file tree
Hide file tree
Showing 2 changed files with 350 additions and 7 deletions.
201 changes: 194 additions & 7 deletions src/mempool.zig
Original file line number Diff line number Diff line change
@@ -1,12 +1,28 @@
const std = @import("std");
const Config = @import("config.zig").Config;
const tx = @import("transaction.zig");

/// Transaction mempool.
/// The mempool is a collection of transactions that are pending for confirmation.
/// The node can implement different mempool strategies.
const Transaction = struct {};

/// Transaction descriptor containing a transaction in the mempool along with additional metadata.
const TxDesc = struct {
tx: *tx.Transaction,
added_time: i64,
height: i32,
fee: i64,
fee_per_kb: i64,
starting_priority: f64,
};

/// Mempool for validating and storing standalone transactions until they are mined into a block.
pub const Mempool = struct {
allocator: std.mem.Allocator,
config: *const Config,
pool: std.AutoHashMap(tx.Hash, *TxDesc),
orphans: std.AutoHashMap(tx.Hash, *tx.Transaction),
orphans_by_prev: std.AutoHashMap(tx.OutPoint, std.AutoHashMap(tx.Hash, *tx.Transaction)),
outpoints: std.AutoHashMap(tx.OutPoint, *tx.Transaction),
last_updated: i64,

/// Initialize the mempool
///
Expand All @@ -15,20 +31,191 @@ pub const Mempool = struct {
/// - `config`: Configuration
///
/// # Returns
/// - `Mempool`: Mempool
/// - `Mempool`: Initialized mempool
pub fn init(allocator: std.mem.Allocator, config: *const Config) !Mempool {
return Mempool{
.allocator = allocator,
.config = config,
.pool = std.AutoHashMap(tx.Hash, *TxDesc).init(allocator),
.orphans = std.AutoHashMap(tx.Hash, *tx.Transaction).init(allocator),
.orphans_by_prev = std.AutoHashMap(tx.OutPoint, std.AutoHashMap(tx.Hash, *tx.Transaction)).init(allocator),
.outpoints = std.AutoHashMap(tx.OutPoint, *tx.Transaction).init(allocator),
.last_updated = 0,
};
}

/// Deinitialize the mempool
pub fn deinit(self: *Mempool) void {
self.pool.deinit();
self.orphans.deinit();
self.orphans_by_prev.deinit();
self.outpoints.deinit();
}

/// Add a transaction to the mempool
///
/// # Arguments
/// - `self`: Mempool
pub fn deinit(self: *Mempool) void {
// Clean up resources if needed
/// - `transaction`: Transaction to add
/// - `height`: Current blockchain height
/// - `fee`: Transaction fee
///
/// # Returns
/// - `?*TxDesc`: Added transaction descriptor or null if not added
pub fn addTransaction(self: *Mempool, transaction: *tx.Transaction, height: i32, fee: i64) !?*TxDesc {
const hash = transaction.hash();

// Check if the transaction is already in the pool
if (self.pool.contains(hash)) {
return null;
}

// Create a new transaction descriptor
const tx_desc = try self.allocator.create(TxDesc);
tx_desc.* = TxDesc{
.tx = transaction,
.added_time = std.time.milliTimestamp(),
.height = height,
.fee = fee,
.fee_per_kb = @divTrunc(fee * 1000, @as(i64, @intCast(transaction.virtual_size()))),
.starting_priority = try self.calculatePriority(transaction, height),
};

// Add the transaction to the pool
try self.pool.put(hash, tx_desc);

// Add the transaction outpoints to the outpoints map
for (transaction.inputs.items) |input| {
try self.outpoints.put(input.previous_outpoint, transaction);
}

// Update the last updated timestamp
self.last_updated = std.time.milliTimestamp();

return tx_desc;
}

/// Remove a transaction from the mempool
///
/// # Arguments
/// - `hash`: Hash of the transaction to remove
/// - `remove_redeemers`: Whether to remove transactions that redeem outputs of this transaction
pub fn removeTransaction(self: *Mempool, hash: tx.Hash, remove_redeemers: bool) void {
const tx_desc = self.pool.get(hash) orelse return;

if (remove_redeemers) {
// Remove any transactions which rely on this one
for (tx_desc.tx.outputs.items, 0..) |_, i| {
const outpoint = tx.OutPoint{ .hash = hash, .index = @as(u32, @intCast(i)) };
if (self.outpoints.get(outpoint)) |redeemer| {
self.removeTransaction(redeemer.hash(), true);
}
}
}

// Remove the transaction from the pool
_ = self.pool.remove(hash);

// Remove the outpoints from the outpoints map
for (tx_desc.tx.inputs.items) |input| {
_ = self.outpoints.remove(input.previous_outpoint);
}

// Update the last updated timestamp
self.last_updated = std.time.milliTimestamp();

// Free the transaction descriptor
self.allocator.destroy(tx_desc);
}

/// Calculate the priority of a transaction
///
/// # Arguments
/// - `transaction`: Transaction to calculate priority for
/// - `height`: Current blockchain height
///
/// # Returns
/// - `f64`: Calculated priority
fn calculatePriority(self: *Mempool, transaction: *tx.Transaction, height: i32) !f64 {
_ = self;
var priority: f64 = 0;
for (transaction.inputs.items) |input| {
// TODO: Fetch the UTXO from the chain
_ = input;
const utxo = .{ .value = 1000, .height = 100 };
const input_value = utxo.value;
const input_age = @as(f64, @floatFromInt(height - utxo.height));
priority += @as(f64, @floatFromInt(input_value)) * input_age;
}

priority /= @as(f64, @floatFromInt(transaction.virtual_size()));

return priority;
}

/// Check if a transaction is in the mempool
///
/// # Arguments
/// - `hash`: Hash of the transaction to check
///
/// # Returns
/// - `bool`: True if the transaction is in the mempool, false otherwise
pub fn containsTransaction(self: *const Mempool, hash: tx.Hash) bool {
return self.pool.contains(hash);
}

/// Get the number of transactions in the mempool
///
/// # Returns
/// - `usize`: Number of transactions in the mempool
pub fn count(self: *const Mempool) usize {
return self.pool.count();
}

/// Get the last time the mempool was updated
///
/// # Returns
/// - `i64`: Last update time in milliseconds
pub fn lastUpdated(self: *const Mempool) i64 {
return self.last_updated;
}
};

test "Mempool" {
const testing = std.testing;
const allocator = testing.allocator;

var config = Config{
.allocator = allocator,
.rpc_port = 8332,
.p2p_port = 8333,
.testnet = false,
.datadir = "/tmp/btczee",
};
var mempool = try Mempool.init(allocator, &config);
defer mempool.deinit();

// Create a mock transaction
var transaction = try tx.Transaction.init(allocator);
defer transaction.deinit();
try transaction.addInput(tx.OutPoint{ .hash = tx.Hash.zero(), .index = 0 });
try transaction.addOutput(50000, try tx.Script.init(allocator));

// Add the transaction to the mempool
const tx_desc = try mempool.addTransaction(&transaction, 101, 1000);
try testing.expect(tx_desc != null);

// Check if the transaction is in the mempool
try testing.expect(mempool.containsTransaction(transaction.hash()));

// Check the mempool count
try testing.expectEqual(@as(usize, 1), mempool.count());

// Remove the transaction from the mempool
mempool.removeTransaction(transaction.hash(), false);

// Check if the transaction is no longer in the mempool
try testing.expect(!mempool.containsTransaction(transaction.hash()));

// Check the mempool count after removal
try testing.expectEqual(@as(usize, 0), mempool.count());
}
156 changes: 156 additions & 0 deletions src/transaction.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
const std = @import("std");

/// Represents a transaction hash
pub const Hash = struct {
bytes: [32]u8,

/// Create a zero hash
pub fn zero() Hash {
return Hash{ .bytes = [_]u8{0} ** 32 };
}

/// Check if two hashes are equal
pub fn eql(self: Hash, other: Hash) bool {
return std.mem.eql(u8, &self.bytes, &other.bytes);
}
};

/// Represents a transaction outpoint (reference to a previous transaction output)
pub const OutPoint = struct {
hash: Hash,
index: u32,
};

/// Represents a transaction input
pub const Input = struct {
previous_outpoint: OutPoint,
script_sig: Script,
sequence: u32,
};

/// Represents a transaction output
pub const Output = struct {
value: i64,
script_pubkey: Script,
};

/// Represents a script (either scriptSig or scriptPubKey)
pub const Script = struct {
bytes: []u8,
allocator: std.mem.Allocator,

/// Initialize a new script
pub fn init(allocator: std.mem.Allocator) !Script {
return Script{
.bytes = try allocator.alloc(u8, 0),
.allocator = allocator,
};
}

/// Deinitialize the script
pub fn deinit(self: *Script) void {
self.allocator.free(self.bytes);
}

/// Add data to the script
pub fn push(self: *Script, data: []const u8) !void {
const new_len = self.bytes.len + data.len;
self.bytes = try self.allocator.realloc(self.bytes, new_len);
@memcpy(self.bytes[self.bytes.len - data.len ..], data);
}
};

/// Represents a transaction
pub const Transaction = struct {
version: i32,
inputs: std.ArrayList(Input),
outputs: std.ArrayList(Output),
lock_time: u32,
allocator: std.mem.Allocator,

/// Initialize a new transaction
pub fn init(allocator: std.mem.Allocator) !Transaction {
return Transaction{
.version = 1,
.inputs = std.ArrayList(Input).init(allocator),
.outputs = std.ArrayList(Output).init(allocator),
.lock_time = 0,
.allocator = allocator,
};
}

/// Deinitialize the transaction
pub fn deinit(self: *Transaction) void {
for (self.inputs.items) |*input| {
input.script_sig.deinit();
}
for (self.outputs.items) |*output| {
output.script_pubkey.deinit();
}
self.inputs.deinit();
self.outputs.deinit();
}

/// Add an input to the transaction
pub fn addInput(self: *Transaction, previous_outpoint: OutPoint) !void {
const script_sig = try Script.init(self.allocator);
try self.inputs.append(Input{
.previous_outpoint = previous_outpoint,
.script_sig = script_sig,
.sequence = 0xffffffff,
});
}

/// Add an output to the transaction
pub fn addOutput(self: *Transaction, value: i64, script_pubkey: Script) !void {
var new_script = try Script.init(self.allocator);
try new_script.push(script_pubkey.bytes);
try self.outputs.append(Output{
.value = value,
.script_pubkey = new_script,
});
}

/// Calculate the transaction hash
pub fn hash(self: *const Transaction) Hash {
var h: [32]u8 = undefined;
std.crypto.hash.sha2.Sha256.hash(@as([]const u8, std.mem.asBytes(&self.version)), &h, .{});
return Hash{ .bytes = h };
}

/// Calculate the virtual size of the transaction
pub fn virtual_size(self: *const Transaction) usize {
// This is a simplified size calculation. In a real implementation,
// you would need to account for segregated witness data if present.
var size: usize = 8; // Version (4 bytes) + LockTime (4 bytes)
size += self.inputs.items.len * 41; // Simplified input size
size += self.outputs.items.len * 33; // Simplified output size
return size;
}
};

test "Transaction basics" {
const testing = std.testing;
const allocator = testing.allocator;

var tx = try Transaction.init(allocator);
defer tx.deinit();

try tx.addInput(OutPoint{ .hash = Hash.zero(), .index = 0 });

{
var script_pubkey = try Script.init(allocator);
defer script_pubkey.deinit();
try script_pubkey.push(&[_]u8{ 0x76, 0xa9, 0x14 }); // OP_DUP OP_HASH160 Push14
try tx.addOutput(50000, script_pubkey);
}

try testing.expectEqual(@as(usize, 1), tx.inputs.items.len);
try testing.expectEqual(@as(usize, 1), tx.outputs.items.len);
try testing.expectEqual(@as(i64, 50000), tx.outputs.items[0].value);

_ = tx.hash();

const vsize = tx.virtual_size();
try testing.expect(vsize > 0);
}

0 comments on commit 8798e20

Please sign in to comment.