Group
Extension

Circle-Miner/lib/Circle/Miner.pm

package Circle::Miner;

use 5.006;
use strict;
use warnings;
use Circle::Common qw(build_url_template http_json_post http_json_get);
use Mojo::IOLoop::Subprocess;
use File::Spec;
use POSIX ();
use Slurp;
use Carp;
use File::Share ':all';

=head1 NAME

Circle::Miner - The miner module for circle chain sdk.

=head1 VERSION

Version 0.03

=cut

our $VERSION = '0.03';

=head1 SYNOPSIS

Circle::Miner example:

    use Circle::Miner;
    use JSON;

    my $miner = Circle::Miner->new();
    my $address = '<your address>';
    my $result = $miner->fetch_my_block($address);
    if ($result->{status} == 200) {
      my $block_header = $result->{blockHeaderHexString};
      my $ip_port = $result->{ipPort};
      my $mined_block = $miner->mine_block($block_header, 2);
      my $result = $miner->post_my_block($address, $mined_block, $ip_port);
      print "post my block result:" . encode_json($result);
    }
    ...

=head1 EXPORT

there are 4 methods for the Circle::Miner module.

=over 4

=item * new  

=item * fetch_my_block

=item * mine_block

=item * post_my_block

=back

=head1 SUBROUTINES/METHODS

=head2 new

Creates a new Circle::Miner object instance.

Parameters:
    None

Returns:
    A new Circle::Miner object instance

Example:
    my $miner = Circle::Miner->new();

=cut

sub new {
    my $class = shift;
    return $class if ref $class;

    return bless {}, $class;
}

=head2 fetch_my_block($address)

Fetches a block for mining based on the provided wallet address.

Parameters:

=over 4

=item * $address - A string representing the wallet address (required)

=back

Returns:
    A hashref containing:

=over 4

=item * status: HTTP status code

=item * data: the data of return

=item * data.blockHeaderHexString: The block header in hex format

=item * data.ipPort: The IP and port of the node

=item * data.channelId: The channel id of the node

=back

Throws:

=over 4

=item * Croaks if address is not provided

=back

Example:

    my $result = $miner->fetch_my_block('1ABC...');
    if ($result->{status} == 200) {
        # Process the block header
    }


=cut

sub fetch_my_block {
    my $self = shift;
    my ($address) = @_;
    if (!$address) {
        croak "address must be provided!";
    }
    my $url = build_url_template(
        'wallet',
        'fetchMyBlock',
        {
            address => $address
        }
    );
    print "fetch my block url: $url\n";
    return http_json_get($url);
}

sub get_os_arch {
    my $os = $^O;
    if ($os =~ /linux/i) {
        $os = 'linux';
    } elsif ($os =~ /darwin/i) {
        $os = 'mac';
    } elsif ($os =~ /win/i) {
        $os = 'windows';
    }

    my $arch = get_arch_by_config((POSIX::uname)[4]);
    carp "get arch by config: $arch";
    return ($os, $arch);
}

sub get_arch_by_config {
    my ($arch) = @_;
    carp "get arch by config parameter: $arch";
    
    # 更完整的架构匹配
    if ($arch =~ /^(?:x86_64|amd64|x64|intel64)/i) {
        return 'x86_64';
    } elsif ($arch =~ /^(?:i[3456]86|x86|ia32)/i) {
        return 'x86';
    } elsif ($arch =~ /^(?:arm64|aarch64|arm64e)/i) {
        return 'arm64';
    } elsif ($arch =~ /^(?:arm|armv[1-7])/i) {
        return 'arm';
    }
    
    # 如果无法识别架构,返回原始值或抛出错误
    carp "Warning: Unknown architecture: $arch";
    return $arch;
}

=head2 mine_block($header_hex, $thread_count)

Mines a block using the provided block header hex string with multi-threading support.

Parameters:

=over 4

=item * $header_hex   - A hex string representing the block header (required)

=item * $thread_count - Number of threads to use for mining (optional, defaults to 1)

=back

Returns:
    The mined block data read from './mined.txt'

Throws:
    Croaks if header_hex is not provided or invalid

Example:

    my $mined_block = $miner->mine_block($block_header, 2);

Notes:

=over 4

=item * Spawns multiple processes based on thread_count

=item * Each process mines a different nonce range

=item * When first process finds solution, others are terminated

=item * Uses external mining executable (ccm) based on OS/architecture

=back

=cut

sub mine_block {
    my $self = shift;
    my ($header_hex, $thread_count) = @_;
    $thread_count //= 1;
    if (!$header_hex) {
        croak "header_hex is required";
    }
    if ($header_hex !~ /^[0-9a-fA-F]+$/) {
        croak "header_hex is invalid";
    }

    my $ranges = $self->make_ranges($thread_count);
    my ($volume, $directory, $file) = File::Spec->splitpath(__FILE__);
    $directory =~s/\/$//;
    my ($os, $arch) = get_os_arch();
    my $config_path;
    if ($os eq 'windows') {
        $config_path = dist_file('Circle-Miner', "$os/$arch/ccm.exe");
    } else {
        $config_path = dist_file('Circle-Miner', "$os/$arch/ccm");
    }
    my $cmd_path = $config_path;
    print "cmd path: $cmd_path\n";
    my @pids;
    foreach my $range (@{$ranges}) {
        my $command = "$cmd_path $range->[0]-$range->[1] $header_hex";
        my $pid = fork;
        if ($pid) {
            push @pids, $pid;
            next;    # Parent goes to next server.
        }
        die "fork failed: $!" unless defined $pid;

        print "run command: $command\n";
        my $result = `$command`;
        print "mined result: $result\n";
        exit;    # Ends the child process.
    }
    my $first_pid = wait();
    # print "found first pid: $first_pid\n";
    if ($first_pid > 0) {
        my @undone_pids = grep { $_ != $first_pid } @pids;
        # print "undone pids:" . encode_json(\@undone_pids) . "\n";
        for my $child_pid (@undone_pids) {
            kill 'SIGTERM',  $child_pid;
            # print "kill pid: $child_pid\n";
        }
    }

    if (-e "./mined.txt") {
        my $content = slurp("./mined.txt");
        my @lines = split /\n/, $content;
        return $lines[0];
    }
    return "";
}

sub make_ranges {
    my $self = shift;
    my ($thread_count) = @_;
    $thread_count //= 1;
    if ($thread_count == 0) {
        $thread_count = 1;
    }
    my $span = int(100 / $thread_count);
    my @ranges;
    for (my $index = 0; $index < $thread_count; $index++) {
        my $start = $span * $index;
        my $end = ($index+1) * $span;
        if ($index + 1 == $thread_count) {
            $end = 100;
        }
        push @ranges, [$start, $end];
    }
    return \@ranges;
}

=head2 post_my_block($address, $header_hex, $channel_id)

Posts a mined block to the network.

Parameters:

=over 4

=item * $address    - The wallet address (required, must be 34 chars and start with '1')

=item * $header_hex - The mined block header in hex format (required)

=item * $channel_id - The channel id to post to (required)

=back

Returns:

=over

=item * status: HTTP status code

=item * data: Response data from the server

=back

Throws:
    Croaks if:

=over 4

=item * Any required parameter is missing

=item * Address format is invalid (length != 34 or doesn't start with '1')

=back

Example:

    my $result = $miner->post_my_block('1ABC...', $mined_block, '0c50c941bb6856b36216c4df1ce5c96815d02c5d5c33ff64c55471f3f1ea1792');
    if ($result->{status} == 200) {
        # Block successfully posted
    }

=cut

sub post_my_block {
    my $self = shift;
    my ( $address, $header_hex, $channel_id ) = @_;
    if (!$address || !$header_hex || !$channel_id) {
        croak "address header_hex and channel_id must be non-empty!";
    }
    if (length($address) != 34 || substr($address, 0, 1) ne "1") {
        croak "address is invalid";
    }
    my $url = build_url_template( 'wallet', 'postMyBlock' );
    return http_json_post(
        $url,
        {
            address              => $address,
            channelId            => $channel_id,
            blockHeaderHexString => $header_hex
        }
    );
}

=head1 AUTHOR

charles li, C<< <lidh04 at gmail.com> >>

=head1 BUGS

Please report any bugs or feature requests to C<bug-circle-miner at rt.cpan.org>, or through
the web interface at L<https://rt.cpan.org/NoAuth/ReportBug.html?Queue=Circle-Miner>.  I will be notified, and then you'll
automatically be notified of progress on your bug as I make changes.




=head1 SUPPORT

You can find documentation for this module with the perldoc command.

    perldoc Circle::Miner


You can also look for information at:

=over 4

=item * RT: CPAN's request tracker (report bugs here)

L<https://rt.cpan.org/NoAuth/Bugs.html?Dist=Circle-Miner>

=item * CPAN Ratings

L<https://cpanratings.perl.org/d/Circle-Miner>

=item * Search CPAN

L<https://metacpan.org/release/Circle-Miner>

=back


=head1 ACKNOWLEDGEMENTS


=head1 LICENSE AND COPYRIGHT

This software is Copyright (c) 2024 by charles li.

This is free software, licensed under:

  The Artistic License 2.0 (GPL Compatible)


=cut

1;    # End of Circle::Miner


Powered by Groonga
Maintained by Kenichi Ishigaki <ishigaki@cpan.org>. If you find anything, submit it on GitHub.