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.
This commit is contained in:
22
bin/sharp-coin
Executable file
22
bin/sharp-coin
Executable file
@@ -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
|
41
doc/AIMS
Normal file
41
doc/AIMS
Normal file
@@ -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
|
14
lib/em-bitcoin.rb
Normal file
14
lib/em-bitcoin.rb
Normal file
@@ -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 <nick@lupine.me.uk>
|
||||||
|
class BitcoinPeer < EventMachine::Connection
|
||||||
|
# TODO!
|
||||||
|
def receive_data(data)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
5
lib/sharp-coin.rb
Normal file
5
lib/sharp-coin.rb
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
require 'sharp-coin/config'
|
||||||
|
require 'sharp-coin/db'
|
||||||
|
require 'sharp-coin/logging'
|
||||||
|
require 'sharp-coin/server'
|
||||||
|
require 'sharp-coin/interface'
|
51
lib/sharp-coin/config.rb
Normal file
51
lib/sharp-coin/config.rb
Normal file
@@ -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 <nick@lupine.me.uk>
|
||||||
|
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<String,Fixnum>] 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
|
14
lib/sharp-coin/db.rb
Normal file
14
lib/sharp-coin/db.rb
Normal file
@@ -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
|
0
lib/sharp-coin/db/schema.rb
Normal file
0
lib/sharp-coin/db/schema.rb
Normal file
13
lib/sharp-coin/interface.rb
Normal file
13
lib/sharp-coin/interface.rb
Normal file
@@ -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 <nick@lupine.me.uk>
|
||||||
|
module Interface
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
9
lib/sharp-coin/interface/bitcoin.rb
Normal file
9
lib/sharp-coin/interface/bitcoin.rb
Normal file
@@ -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 <nick@lupine.me.uk>
|
||||||
|
class Bitcoin
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
15
lib/sharp-coin/interface/json-rpc.rb
Normal file
15
lib/sharp-coin/interface/json-rpc.rb
Normal file
@@ -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 <nick@lupine.me.uk>
|
||||||
|
class JsonRpc < Sinatra::Base
|
||||||
|
end
|
||||||
|
JsonRPC = JsonRpc
|
||||||
|
JSONRPC = JsonRpc
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
14
lib/sharp-coin/interface/rest.rb
Normal file
14
lib/sharp-coin/interface/rest.rb
Normal file
@@ -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 <nick@lupine.me.uk>
|
||||||
|
class Rest < Sinatra::Base
|
||||||
|
end
|
||||||
|
REST = Rest
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
15
lib/sharp-coin/interface/telnet.rb
Normal file
15
lib/sharp-coin/interface/telnet.rb
Normal file
@@ -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 <nick@lupine.me.uk>
|
||||||
|
class Telnet < EM::P::LineAndTextProtocol
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
26
lib/sharp-coin/interface/web.rb
Normal file
26
lib/sharp-coin/interface/web.rb
Normal file
@@ -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 <nick@lupine.me.uk>
|
||||||
|
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
|
||||||
|
|
41
lib/sharp-coin/logging.rb
Normal file
41
lib/sharp-coin/logging.rb
Normal file
@@ -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 <nick@lupine.me.uk>
|
||||||
|
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 <nick@lupine.me.uk>
|
||||||
|
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
|
||||||
|
|
41
lib/sharp-coin/server.rb
Normal file
41
lib/sharp-coin/server.rb
Normal file
@@ -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 <nick@lupine.me.uk>
|
||||||
|
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
|
0
sharp-coin.config.yaml
Normal file
0
sharp-coin.config.yaml
Normal file
Reference in New Issue
Block a user