commit 5554ff2dbe3980eeacb5150a11d4181cc66f9c92 Author: Nicholas Thomas Date: Sun May 15 00:36:57 2011 +0100 Starting point None of this code is final - indeed, most of it is just gubbins - but it shows the path I mean to take with the code, I hope. Much more to come. diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..e69de29 diff --git a/bin/sharp-coin b/bin/sharp-coin new file mode 100755 index 0000000..360a5e2 --- /dev/null +++ b/bin/sharp-coin @@ -0,0 +1,22 @@ +#!/usr/bin/env ruby + +$: << 'lib' # TODO: remove this + +require 'sharp-coin' + +SharpCoin::Config::read(ARGV[0]) + +server = SharpCoin::Server.new + +trap("INT") do + server.stop +end + +trap("KILL") do + server.stop +end + +EM::run do + EventMachine.epoll + server.run +end diff --git a/doc/AIMS b/doc/AIMS new file mode 100644 index 0000000..db209db --- /dev/null +++ b/doc/AIMS @@ -0,0 +1,41 @@ +=== To produce a bitcoin application === + +This will be a web application, written in ruby (using appropriate technologies) +that can be run standalone, for a single user on their home machine, or on a web +server for many users. + +Needs to be feature-complete by comparison to the official bitcoin client, with +the exception of block generation - we're not bothering with that aspect at all +right now. Also needs feature parity with mybitcoin.com + +List: + - Multiple users + - Each user has a wallet + - Ability to receive payments + - Transaction history + - Ability to make payments, specify fee per-payment + - Able to take part in the global bitcoin network as a full member. + - Shopping cart integration + - On-the-fly currency comparison + - Payment forwarding + - API access via the JSON-RPC specification to control the account + +Also needs *extra* features + + - Only one block chain for all users in the cluster (faster startup time) + - (mybitcoin) : open-source, run it for yourself if you want to! + - britcoin, bitmarket, mtgox integration + - Address book + - Support for sending payments to email addresses - use webfinger to resolve + - Simpler REST API + +=== Technology === + +Runtime: Ruby 1.9.2-p180 +Web server: Thin +Templating: haml, sass, coffeescript +ORM: ActiveRecord +Live action: SSE (depends: thin-async) +Key generation: OpenSSL +Database: standalone: SQLite3/SQLCipher. All-in: postgres/mysql +Bitcoin network peer: event-machine, bit-struct diff --git a/lib/em-bitcoin.rb b/lib/em-bitcoin.rb new file mode 100644 index 0000000..509b3b3 --- /dev/null +++ b/lib/em-bitcoin.rb @@ -0,0 +1,14 @@ +require 'eventmachine' + +module EventMachine + module Protocols + # Implements the TCP protocol that Bitcoin peers speak to each other. This + # class can be used for both incoming and outgoing connections + # @author Nick Thomas + class BitcoinPeer < EventMachine::Connection + # TODO! + def receive_data(data) + end + end + end +end diff --git a/lib/sharp-coin.rb b/lib/sharp-coin.rb new file mode 100644 index 0000000..9087a90 --- /dev/null +++ b/lib/sharp-coin.rb @@ -0,0 +1,5 @@ +require 'sharp-coin/config' +require 'sharp-coin/db' +require 'sharp-coin/logging' +require 'sharp-coin/server' +require 'sharp-coin/interface' diff --git a/lib/sharp-coin/config.rb b/lib/sharp-coin/config.rb new file mode 100644 index 0000000..2d3c939 --- /dev/null +++ b/lib/sharp-coin/config.rb @@ -0,0 +1,51 @@ +require 'yaml' +require 'blankslate' + +module SharpCoin + # Parses the sharp-coin.config.yaml file and provides access to all its + # settings in one place. This is a singleton. + # @author Nick Thomas + class Config < BlankSlate + def initialize + raise ArgumentError.new("Singleton class!") + end + + class << self + + # Load the configuration file into memory. + # @param[String] filename Configuration file + # @return[SharpCoin::Config] self + def read(filename) + @config = YAML::parse_file(filename) + end + + def http_host + "localhost" + end + + def http_port + 3000 + end + + # @return[Array] Address and port that the HTTP server + # should bind to. + def http_bind + [http_host, http_port] + end + + def telnet_host + "localhost" + end + + def telnet_port + 3001 + end + + def telnet_bind + [telnet_host, telnet_port] + end + + end + + end +end diff --git a/lib/sharp-coin/db.rb b/lib/sharp-coin/db.rb new file mode 100644 index 0000000..59b6c19 --- /dev/null +++ b/lib/sharp-coin/db.rb @@ -0,0 +1,14 @@ +gem 'activerecord', '3.0.7' # FIXME: Ugh + +require 'active_record' + +module SharpCoin + module DB + class << self + def setup! + + end + + end + end +end diff --git a/lib/sharp-coin/db/schema.rb b/lib/sharp-coin/db/schema.rb new file mode 100644 index 0000000..e69de29 diff --git a/lib/sharp-coin/interface.rb b/lib/sharp-coin/interface.rb new file mode 100644 index 0000000..914985b --- /dev/null +++ b/lib/sharp-coin/interface.rb @@ -0,0 +1,13 @@ +require 'sharp-coin/interface/json-rpc' +require 'sharp-coin/interface/rest' +require 'sharp-coin/interface/telnet' +require 'sharp-coin/interface/web' + +module SharpCoin + # Hooks to start and stop the various interfaces that the + # @author Nick Thomas + module Interface + + end + +end diff --git a/lib/sharp-coin/interface/bitcoin.rb b/lib/sharp-coin/interface/bitcoin.rb new file mode 100644 index 0000000..be89550 --- /dev/null +++ b/lib/sharp-coin/interface/bitcoin.rb @@ -0,0 +1,9 @@ +module SharpCoin + module Interface + # Handle communications with other BitCoin peers. We rely on + # EM::P::BitcoinPeer for much of the heavy lifting. + # @author Nick Thomas + class Bitcoin + end + end +end diff --git a/lib/sharp-coin/interface/json-rpc.rb b/lib/sharp-coin/interface/json-rpc.rb new file mode 100644 index 0000000..8620e8d --- /dev/null +++ b/lib/sharp-coin/interface/json-rpc.rb @@ -0,0 +1,15 @@ +require 'sinatra/base' +module SharpCoin + module Interface + + # JSON-RPC API for SharpCoin. Used by legacy clients to access accounts + # and do clever things. + # + # @author Nick Thomas + class JsonRpc < Sinatra::Base + end + JsonRPC = JsonRpc + JSONRPC = JsonRpc + end +end + diff --git a/lib/sharp-coin/interface/rest.rb b/lib/sharp-coin/interface/rest.rb new file mode 100644 index 0000000..5666a6c --- /dev/null +++ b/lib/sharp-coin/interface/rest.rb @@ -0,0 +1,14 @@ +require 'sinatra/base' +module SharpCoin + module Interface + + # REST API for SharpCoin. Used by the web interface and clever clients to + # do things. + # + # @author Nick Thomas + class Rest < Sinatra::Base + end + REST = Rest + end +end + diff --git a/lib/sharp-coin/interface/telnet.rb b/lib/sharp-coin/interface/telnet.rb new file mode 100644 index 0000000..db89468 --- /dev/null +++ b/lib/sharp-coin/interface/telnet.rb @@ -0,0 +1,15 @@ +require 'em/protocols/line_and_text' + +module SharpCoin + module Interface + + # Telnet API for SharpCoin. Used for debugging and administrative tasks + # + # @author Nick Thomas + class Telnet < EM::P::LineAndTextProtocol + + end + + end +end + diff --git a/lib/sharp-coin/interface/web.rb b/lib/sharp-coin/interface/web.rb new file mode 100644 index 0000000..c463cad --- /dev/null +++ b/lib/sharp-coin/interface/web.rb @@ -0,0 +1,26 @@ +require 'sinatra/base' +require 'sharp-coin/interface/rest' + +module SharpCoin + module Interface + + # Human-usable web application for SharpCoin. Also mediates access to the + # two API end-points (JSON-RPC and REST). Is basically a skinny server for + # the templates found in sharp-coin/tmpl, which form the web application. + # Most of the web application work is actually done by talking to the + # REST API! + # + # @author Nick Thomas + class WebReal < Sinatra::Base + + end + + Web = Rack::Builder.new do + map("/api/rpc/") { run JsonRPC } + map("/api/rest/") { run REST } + map("/") { run WebReal } + end + + end +end + diff --git a/lib/sharp-coin/logging.rb b/lib/sharp-coin/logging.rb new file mode 100644 index 0000000..0d31583 --- /dev/null +++ b/lib/sharp-coin/logging.rb @@ -0,0 +1,41 @@ +require 'log4r' +require 'blankslate' + +module SharpCoin + # If an object doesn't have a logger, we 'log' to this instead. + # @author Nick Thomas + class DummyLogger < BlankSlate + def method_missing(*args) + STDERR.puts("DummyLogger: #{args.inspect}") + end + end + + # Include this in any class where you want logging to go to a single place. + # Controlled by SharpCoin::Config, obviously. + # @author Nick Thomas + module Logging + class << self + def logger + nil + end + end + + def logger + @logger || self.class.logger || DummyLogger.new + end + + # @param[Array[Symbol,String]|Array[String]] Data to log. Can specify a log + # level and a message, or just a message. If the latter, then we default + # to info + def log(*args) + case args.size + when 1 then logger.info(args[0]) + when 2 then logger.send(*args) + else + logger.warn("Bad method signature for log. Args: #{args.inspect}") + end + end + + end +end + diff --git a/lib/sharp-coin/server.rb b/lib/sharp-coin/server.rb new file mode 100644 index 0000000..954d258 --- /dev/null +++ b/lib/sharp-coin/server.rb @@ -0,0 +1,41 @@ +require 'eventmachine' +require 'thin' + +require 'sharp-coin/db' +require 'sharp-coin/interface' +module SharpCoin + # Beating heart of the SharpCoin application. Sets up all the components + # according to the config, handles all the events as needed. + # @author Nick Thomas + class Server + include Logging + + # Create a new server instance. This instance coordinates the various bits + # of SharpCoin to produce a working application. + # @param[ + def initialize + DB::setup! + end + + # Start the various services off. This should generally be called inside an + # EM::run { ... } block + def run + @running = true + @thin = Thin::Server.start(Interface::Web, *(Config::http_bind)) + @telnet = EM::start_server(*([Config::telnet_bind, Interface::Telnet].flatten)) + end + + # @return[Boolean] Is this server instance currently running? + def running? + @running == true + end + + # Stop the various services. + def stop + @thin.stop + @telnet.stop + @running = false + end + + end +end diff --git a/sharp-coin.config.yaml b/sharp-coin.config.yaml new file mode 100644 index 0000000..e69de29