tapyrus 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.rspec +2 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +12 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +100 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/exe/tapyrusrb-cli +5 -0
- data/exe/tapyrusrbd +41 -0
- data/lib/openassets/marker_output.rb +20 -0
- data/lib/openassets/payload.rb +54 -0
- data/lib/openassets/util.rb +28 -0
- data/lib/openassets.rb +9 -0
- data/lib/tapyrus/base58.rb +38 -0
- data/lib/tapyrus/block.rb +77 -0
- data/lib/tapyrus/block_header.rb +88 -0
- data/lib/tapyrus/bloom_filter.rb +78 -0
- data/lib/tapyrus/chain_params.rb +90 -0
- data/lib/tapyrus/chainparams/mainnet.yml +41 -0
- data/lib/tapyrus/chainparams/regtest.yml +38 -0
- data/lib/tapyrus/chainparams/testnet.yml +41 -0
- data/lib/tapyrus/constants.rb +195 -0
- data/lib/tapyrus/descriptor.rb +147 -0
- data/lib/tapyrus/ext_key.rb +337 -0
- data/lib/tapyrus/key.rb +296 -0
- data/lib/tapyrus/key_path.rb +26 -0
- data/lib/tapyrus/logger.rb +42 -0
- data/lib/tapyrus/merkle_tree.rb +149 -0
- data/lib/tapyrus/message/addr.rb +35 -0
- data/lib/tapyrus/message/base.rb +28 -0
- data/lib/tapyrus/message/block.rb +46 -0
- data/lib/tapyrus/message/block_transaction_request.rb +45 -0
- data/lib/tapyrus/message/block_transactions.rb +31 -0
- data/lib/tapyrus/message/block_txn.rb +27 -0
- data/lib/tapyrus/message/cmpct_block.rb +42 -0
- data/lib/tapyrus/message/error.rb +10 -0
- data/lib/tapyrus/message/fee_filter.rb +27 -0
- data/lib/tapyrus/message/filter_add.rb +28 -0
- data/lib/tapyrus/message/filter_clear.rb +17 -0
- data/lib/tapyrus/message/filter_load.rb +39 -0
- data/lib/tapyrus/message/get_addr.rb +17 -0
- data/lib/tapyrus/message/get_block_txn.rb +27 -0
- data/lib/tapyrus/message/get_blocks.rb +29 -0
- data/lib/tapyrus/message/get_data.rb +21 -0
- data/lib/tapyrus/message/get_headers.rb +28 -0
- data/lib/tapyrus/message/header_and_short_ids.rb +57 -0
- data/lib/tapyrus/message/headers.rb +35 -0
- data/lib/tapyrus/message/headers_parser.rb +24 -0
- data/lib/tapyrus/message/inv.rb +21 -0
- data/lib/tapyrus/message/inventories_parser.rb +23 -0
- data/lib/tapyrus/message/inventory.rb +51 -0
- data/lib/tapyrus/message/mem_pool.rb +17 -0
- data/lib/tapyrus/message/merkle_block.rb +42 -0
- data/lib/tapyrus/message/network_addr.rb +63 -0
- data/lib/tapyrus/message/not_found.rb +21 -0
- data/lib/tapyrus/message/ping.rb +30 -0
- data/lib/tapyrus/message/pong.rb +26 -0
- data/lib/tapyrus/message/prefilled_tx.rb +29 -0
- data/lib/tapyrus/message/reject.rb +46 -0
- data/lib/tapyrus/message/send_cmpct.rb +43 -0
- data/lib/tapyrus/message/send_headers.rb +16 -0
- data/lib/tapyrus/message/tx.rb +30 -0
- data/lib/tapyrus/message/ver_ack.rb +17 -0
- data/lib/tapyrus/message/version.rb +69 -0
- data/lib/tapyrus/message.rb +70 -0
- data/lib/tapyrus/mnemonic/wordlist/chinese_simplified.txt +2048 -0
- data/lib/tapyrus/mnemonic/wordlist/chinese_traditional.txt +2048 -0
- data/lib/tapyrus/mnemonic/wordlist/english.txt +2048 -0
- data/lib/tapyrus/mnemonic/wordlist/french.txt +2048 -0
- data/lib/tapyrus/mnemonic/wordlist/italian.txt +2048 -0
- data/lib/tapyrus/mnemonic/wordlist/japanese.txt +2048 -0
- data/lib/tapyrus/mnemonic/wordlist/spanish.txt +2048 -0
- data/lib/tapyrus/mnemonic.rb +77 -0
- data/lib/tapyrus/network/connection.rb +73 -0
- data/lib/tapyrus/network/message_handler.rb +241 -0
- data/lib/tapyrus/network/peer.rb +223 -0
- data/lib/tapyrus/network/peer_discovery.rb +42 -0
- data/lib/tapyrus/network/pool.rb +135 -0
- data/lib/tapyrus/network.rb +13 -0
- data/lib/tapyrus/node/cli.rb +112 -0
- data/lib/tapyrus/node/configuration.rb +38 -0
- data/lib/tapyrus/node/spv.rb +79 -0
- data/lib/tapyrus/node.rb +7 -0
- data/lib/tapyrus/opcodes.rb +178 -0
- data/lib/tapyrus/out_point.rb +44 -0
- data/lib/tapyrus/rpc/http_server.rb +65 -0
- data/lib/tapyrus/rpc/request_handler.rb +150 -0
- data/lib/tapyrus/rpc/tapyrus_core_client.rb +72 -0
- data/lib/tapyrus/rpc.rb +7 -0
- data/lib/tapyrus/script/multisig.rb +92 -0
- data/lib/tapyrus/script/script.rb +551 -0
- data/lib/tapyrus/script/script_error.rb +111 -0
- data/lib/tapyrus/script/script_interpreter.rb +668 -0
- data/lib/tapyrus/script/tx_checker.rb +81 -0
- data/lib/tapyrus/script_witness.rb +38 -0
- data/lib/tapyrus/secp256k1/native.rb +174 -0
- data/lib/tapyrus/secp256k1/ruby.rb +123 -0
- data/lib/tapyrus/secp256k1.rb +12 -0
- data/lib/tapyrus/slip39/share.rb +122 -0
- data/lib/tapyrus/slip39/sss.rb +245 -0
- data/lib/tapyrus/slip39/wordlist/english.txt +1024 -0
- data/lib/tapyrus/slip39.rb +93 -0
- data/lib/tapyrus/store/chain_entry.rb +67 -0
- data/lib/tapyrus/store/db/level_db.rb +98 -0
- data/lib/tapyrus/store/db.rb +9 -0
- data/lib/tapyrus/store/spv_chain.rb +101 -0
- data/lib/tapyrus/store.rb +9 -0
- data/lib/tapyrus/tx.rb +347 -0
- data/lib/tapyrus/tx_in.rb +89 -0
- data/lib/tapyrus/tx_out.rb +74 -0
- data/lib/tapyrus/util.rb +133 -0
- data/lib/tapyrus/validation.rb +115 -0
- data/lib/tapyrus/version.rb +3 -0
- data/lib/tapyrus/wallet/account.rb +151 -0
- data/lib/tapyrus/wallet/base.rb +162 -0
- data/lib/tapyrus/wallet/db.rb +81 -0
- data/lib/tapyrus/wallet/master_key.rb +110 -0
- data/lib/tapyrus/wallet.rb +8 -0
- data/lib/tapyrus.rb +219 -0
- data/tapyrusrb.conf.sample +0 -0
- data/tapyrusrb.gemspec +47 -0
- metadata +451 -0
@@ -0,0 +1,135 @@
|
|
1
|
+
module Tapyrus
|
2
|
+
|
3
|
+
module Network
|
4
|
+
|
5
|
+
# Time between pings automatically sent out for latency probing and keepalive (in seconds).
|
6
|
+
PING_INTERVAL = 2 * 60
|
7
|
+
# Time after which to disconnect, after waiting for a ping response (or inactivity).
|
8
|
+
TIMEOUT_INTERVAL = 20 * 60
|
9
|
+
# Maximum number of automatic outgoing nodes
|
10
|
+
MAX_OUTBOUND_CONNECTIONS = 4
|
11
|
+
|
12
|
+
# peer pool class.
|
13
|
+
class Pool
|
14
|
+
include Observable
|
15
|
+
|
16
|
+
attr_reader :peers # active peers
|
17
|
+
attr_reader :pending_peers # currently connecting peer
|
18
|
+
attr_reader :node
|
19
|
+
attr_reader :chain
|
20
|
+
attr_reader :max_outbound
|
21
|
+
attr_reader :logger
|
22
|
+
attr_reader :peer_discovery
|
23
|
+
attr_accessor :started
|
24
|
+
attr_reader :mutex
|
25
|
+
|
26
|
+
def initialize(node, chain, configuration)
|
27
|
+
@node = node
|
28
|
+
@peers = []
|
29
|
+
@pending_peers = []
|
30
|
+
@max_outbound = MAX_OUTBOUND_CONNECTIONS
|
31
|
+
@chain = chain
|
32
|
+
@logger = Tapyrus::Logger.create(:debug)
|
33
|
+
@configuration = configuration
|
34
|
+
@peer_discovery = PeerDiscovery.new(configuration)
|
35
|
+
@started = false
|
36
|
+
@mutex = Mutex.new
|
37
|
+
end
|
38
|
+
|
39
|
+
# connecting other peers and begin network activity.
|
40
|
+
def start
|
41
|
+
raise 'Cannot start a peer pool twice.' if started
|
42
|
+
logger.debug 'Start connecting other pears.'
|
43
|
+
addr_list = peer_discovery.peers
|
44
|
+
|
45
|
+
connect(addr_list)
|
46
|
+
|
47
|
+
@started = true
|
48
|
+
end
|
49
|
+
|
50
|
+
# detect new peer connection.
|
51
|
+
def handle_new_peer(peer)
|
52
|
+
logger.debug "connected new peer #{peer.addr}."
|
53
|
+
mutex.synchronize do
|
54
|
+
peer.id = allocate_peer_id
|
55
|
+
unless peers.find(&:primary?)
|
56
|
+
peer.primary = true
|
57
|
+
peer.start_block_header_download
|
58
|
+
end
|
59
|
+
peers << peer
|
60
|
+
end
|
61
|
+
pending_peers.delete(peer)
|
62
|
+
filter_load(peer) if node.wallet
|
63
|
+
end
|
64
|
+
|
65
|
+
def handle_close_peer(peer)
|
66
|
+
return unless started
|
67
|
+
peers.delete(peer)
|
68
|
+
pending_peers.delete(peer)
|
69
|
+
addr_list = peer_discovery.peers - peers.map(&:host) - pending_peers.map(&:host) - [peer.host]
|
70
|
+
connect(addr_list)
|
71
|
+
end
|
72
|
+
|
73
|
+
# terminate peers.
|
74
|
+
def terminate
|
75
|
+
peers.each { |peer| peer.close('terminate') }
|
76
|
+
pending_peers.each { |peer| peer.close('terminate') }
|
77
|
+
@peers = []
|
78
|
+
@started = false
|
79
|
+
end
|
80
|
+
|
81
|
+
# broadcast tx to connecting peer.
|
82
|
+
def broadcast(tx)
|
83
|
+
peers.each { |peer| peer.broadcast_tx(tx) }
|
84
|
+
end
|
85
|
+
|
86
|
+
# new bloom filter.
|
87
|
+
def filter_load(peer)
|
88
|
+
peer.send_filter_load(node.bloom)
|
89
|
+
end
|
90
|
+
|
91
|
+
# add element to bloom filter.
|
92
|
+
def filter_add(element)
|
93
|
+
peers.each { |peer| peer.send_filter_add(element) }
|
94
|
+
end
|
95
|
+
|
96
|
+
# clear bloom filter.
|
97
|
+
def filter_clear
|
98
|
+
peers.each { |peer| peer.send_filter_clear }
|
99
|
+
end
|
100
|
+
|
101
|
+
def handle_error(e)
|
102
|
+
terminate
|
103
|
+
end
|
104
|
+
|
105
|
+
private
|
106
|
+
|
107
|
+
# get primary peer
|
108
|
+
def primary_peer
|
109
|
+
peers.find(&:primary?)
|
110
|
+
end
|
111
|
+
|
112
|
+
# allocate new peer id
|
113
|
+
def allocate_peer_id
|
114
|
+
id = 0
|
115
|
+
until peers.empty? || peers.find{|p|p.id == id}.nil?
|
116
|
+
id += 1
|
117
|
+
end
|
118
|
+
id
|
119
|
+
end
|
120
|
+
|
121
|
+
def connect(addr_list)
|
122
|
+
port = Tapyrus.chain_params.default_port
|
123
|
+
|
124
|
+
EM::Iterator.new(addr_list, Tapyrus::PARALLEL_THREAD).each do |ip, iter|
|
125
|
+
if pending_peers.size + peers.size < MAX_OUTBOUND_CONNECTIONS
|
126
|
+
peer = Peer.new(ip, port, self, @configuration)
|
127
|
+
pending_peers << peer
|
128
|
+
peer.connect
|
129
|
+
iter.next
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'eventmachine'
|
2
|
+
|
3
|
+
module Tapyrus
|
4
|
+
module Network
|
5
|
+
|
6
|
+
autoload :MessageHandler, 'tapyrus/network/message_handler'
|
7
|
+
autoload :Connection, 'tapyrus/network/connection'
|
8
|
+
autoload :Pool, 'tapyrus/network/pool'
|
9
|
+
autoload :Peer, 'tapyrus/network/peer'
|
10
|
+
autoload :PeerDiscovery, 'tapyrus/network/peer_discovery'
|
11
|
+
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'rest-client'
|
2
|
+
require 'thor'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module Tapyrus
|
6
|
+
module Node
|
7
|
+
|
8
|
+
class CLI < Thor
|
9
|
+
|
10
|
+
class_option :network, aliases: '-n', default: :mainnet
|
11
|
+
|
12
|
+
desc 'getblockchaininfo', 'Returns an object containing various state info regarding blockchain processing.'
|
13
|
+
def getblockchaininfo
|
14
|
+
request('getblockchaininfo')
|
15
|
+
end
|
16
|
+
|
17
|
+
desc 'stop', 'Stop Tapyrus server.'
|
18
|
+
def stop
|
19
|
+
request('stop')
|
20
|
+
end
|
21
|
+
|
22
|
+
desc 'getblockheader "hash" ( verbose )', 'If verbose is false, returns a string that is serialized, hex-encoded data for blockheader "hash". If verbose is true, returns an Object with information about blockheader <hash>.'
|
23
|
+
def getblockheader(hash, verbose = true)
|
24
|
+
verbose = verbose.is_a?(String) ? (verbose == 'true') : verbose
|
25
|
+
request('getblockheader', hash, verbose)
|
26
|
+
end
|
27
|
+
|
28
|
+
desc 'getpeerinfo', 'Returns data about each connected network node as a json array of objects.'
|
29
|
+
def getpeerinfo
|
30
|
+
request('getpeerinfo')
|
31
|
+
end
|
32
|
+
|
33
|
+
desc 'decoderawtransaction "hexstring"', 'Return a JSON object representing the serialized, hex-encoded transaction.'
|
34
|
+
def decoderawtransaction(hexstring)
|
35
|
+
request('decoderawtransaction', hexstring)
|
36
|
+
end
|
37
|
+
|
38
|
+
desc 'decodescript "hexstring"', 'Decode a hex-encoded script.'
|
39
|
+
def decodescript(hexstring)
|
40
|
+
request('decodescript', hexstring)
|
41
|
+
end
|
42
|
+
|
43
|
+
# wallet cli
|
44
|
+
|
45
|
+
desc 'sendrawtransaction', 'Submits raw transaction (serialized, hex-encoded) to local node and network.'
|
46
|
+
def sendrawtransaction(hex_tx)
|
47
|
+
request('sendrawtransaction', hex_tx)
|
48
|
+
end
|
49
|
+
|
50
|
+
desc 'createwallet "wallet_id"', 'Create new HD wallet. It returns an error if an existing wallet_id is specified. '
|
51
|
+
def createwallet(wallet_id)
|
52
|
+
request('createwallet', wallet_id)
|
53
|
+
end
|
54
|
+
|
55
|
+
desc 'listwallets', 'Returns a list of currently loaded wallets. For full information on the wallet, use "getwalletinfo"'
|
56
|
+
def listwallets
|
57
|
+
request('listwallets')
|
58
|
+
end
|
59
|
+
|
60
|
+
desc 'getwalletinfo', 'Returns an object containing various wallet state info.'
|
61
|
+
def getwalletinfo
|
62
|
+
request('getwalletinfo')
|
63
|
+
end
|
64
|
+
|
65
|
+
desc 'listaccounts', '[WIP]Returns Object that has account names as keys, account balances as values.'
|
66
|
+
def listaccounts
|
67
|
+
request('listaccounts')
|
68
|
+
end
|
69
|
+
|
70
|
+
desc 'encryptwallet "passphrase"', 'Encrypts the wallet with "passphrase". This is for first time encryption.After this, any calls that interact with private keys such as sending or signing will require the passphrase to be set prior the making these calls.'
|
71
|
+
def encryptwallet(passhphrase)
|
72
|
+
request('encryptwallet', passhphrase)
|
73
|
+
end
|
74
|
+
|
75
|
+
desc 'getnewaddress "account"', 'Returns a new Tapyrus address for receiving payments.'
|
76
|
+
def getnewaddress(account)
|
77
|
+
request('getnewaddress', account)
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def config
|
83
|
+
opts = {}
|
84
|
+
opts[:network] = options['network'] if options['network']
|
85
|
+
@conf ||= Tapyrus::Node::Configuration.new(opts)
|
86
|
+
end
|
87
|
+
|
88
|
+
def request(command, *params)
|
89
|
+
data = {
|
90
|
+
:method => command,
|
91
|
+
:params => params,
|
92
|
+
:id => 'jsonrpc'
|
93
|
+
}
|
94
|
+
begin
|
95
|
+
RestClient::Request.execute(method: :post, url: config.server_url, payload: data.to_json,
|
96
|
+
headers: {content_type: :json}) do |response, request, result|
|
97
|
+
return false if !result.kind_of?(Net::HTTPSuccess) && response.empty?
|
98
|
+
begin
|
99
|
+
json = JSON.parse(response.to_str)
|
100
|
+
puts JSON.pretty_generate(json)
|
101
|
+
rescue Exception
|
102
|
+
puts response.to_str
|
103
|
+
end
|
104
|
+
end
|
105
|
+
rescue Exception => e
|
106
|
+
puts e.message
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'iniparse'
|
2
|
+
|
3
|
+
module Tapyrus
|
4
|
+
module Node
|
5
|
+
class Configuration
|
6
|
+
|
7
|
+
attr_reader :conf
|
8
|
+
|
9
|
+
def initialize(opts = {})
|
10
|
+
# TODO apply configuration file.
|
11
|
+
opts[:network] = :mainnet unless opts[:network]
|
12
|
+
opts[:relay] = false unless opts[:relay]
|
13
|
+
Tapyrus.chain_params = opts[:network]
|
14
|
+
|
15
|
+
begin
|
16
|
+
ini_file = IniParse.parse(File.read("#{Tapyrus.base_dir}/tapyrusrb.conf"))
|
17
|
+
@conf = Hash[ ini_file.to_h['__anonymous__'].map{|k,v| [k.to_sym, v] } ]
|
18
|
+
rescue => e
|
19
|
+
@conf = {}
|
20
|
+
end
|
21
|
+
@conf.merge!(opts)
|
22
|
+
end
|
23
|
+
|
24
|
+
def host
|
25
|
+
'localhost'
|
26
|
+
end
|
27
|
+
|
28
|
+
def port
|
29
|
+
Tapyrus.chain_params.default_port - 1
|
30
|
+
end
|
31
|
+
|
32
|
+
def server_url
|
33
|
+
"http://#{host}:#{port}"
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module Tapyrus
|
2
|
+
module Node
|
3
|
+
|
4
|
+
# SPV class
|
5
|
+
class SPV
|
6
|
+
|
7
|
+
attr_reader :chain
|
8
|
+
attr_reader :pool
|
9
|
+
attr_reader :logger
|
10
|
+
attr_accessor :running
|
11
|
+
attr_reader :configuration
|
12
|
+
attr_accessor :server
|
13
|
+
attr_accessor :wallet
|
14
|
+
attr_accessor :bloom
|
15
|
+
|
16
|
+
def initialize(configuration)
|
17
|
+
@chain = Tapyrus::Store::SPVChain.new
|
18
|
+
@configuration = configuration
|
19
|
+
@pool = Tapyrus::Network::Pool.new(self, @chain, @configuration)
|
20
|
+
@logger = Tapyrus::Logger.create(:debug)
|
21
|
+
@running = false
|
22
|
+
@wallet = Tapyrus::Wallet::Base.current_wallet
|
23
|
+
# TODO : optimize bloom filter parameters
|
24
|
+
setup_filter
|
25
|
+
end
|
26
|
+
|
27
|
+
# open the node.
|
28
|
+
def run
|
29
|
+
# TODO need process running check.
|
30
|
+
return if running
|
31
|
+
logger.debug 'SPV node start running.'
|
32
|
+
EM.run do
|
33
|
+
# EM.start_server('0.0.0.0', Tapyrus.chain_params.default_port, Tapyrus::Network::InboundConnector, self)
|
34
|
+
pool.start
|
35
|
+
@server = Tapyrus::RPC::HttpServer.run(self, configuration.port)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# close the node.
|
40
|
+
def shutdown
|
41
|
+
pool.terminate
|
42
|
+
logger.debug 'SPV node shutdown.'
|
43
|
+
end
|
44
|
+
|
45
|
+
# broadcast a transaction
|
46
|
+
def broadcast(tx)
|
47
|
+
pool.broadcast(tx)
|
48
|
+
logger.debug "broadcast tx: #{tx.to_payload.bth}"
|
49
|
+
end
|
50
|
+
|
51
|
+
# add filter element to bloom filter.
|
52
|
+
# [String] element. the hex string of txid, public key, public key hash or outpoint.
|
53
|
+
def filter_add(element)
|
54
|
+
bloom.add(element)
|
55
|
+
pool.filter_add(element)
|
56
|
+
end
|
57
|
+
|
58
|
+
# clear bloom filter.
|
59
|
+
def filter_clear
|
60
|
+
pool.filter_clear
|
61
|
+
end
|
62
|
+
|
63
|
+
def add_observer(observer)
|
64
|
+
pool.add_observer(observer)
|
65
|
+
end
|
66
|
+
|
67
|
+
def delete_observer(observer)
|
68
|
+
pool.delete_observer(observer)
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def setup_filter
|
74
|
+
@bloom = Tapyrus::BloomFilter.create_filter(512, 0.01)
|
75
|
+
wallet.watch_targets.each{|t|bloom.add(t.htb)} if wallet
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
data/lib/tapyrus/node.rb
ADDED
@@ -0,0 +1,178 @@
|
|
1
|
+
module Tapyrus
|
2
|
+
|
3
|
+
# https://bitcoin.org/en/developer-reference#opcodes
|
4
|
+
module Opcodes
|
5
|
+
|
6
|
+
module_function
|
7
|
+
|
8
|
+
# https://en.bitcoin.it/wiki/Script#Constants
|
9
|
+
OP_0 = 0x00
|
10
|
+
OP_1 = 0x51
|
11
|
+
OP_2 = 0x52
|
12
|
+
OP_3 = 0x53
|
13
|
+
OP_4 = 0x54
|
14
|
+
OP_5 = 0x55
|
15
|
+
OP_6 = 0x56
|
16
|
+
OP_7 = 0x57
|
17
|
+
OP_8 = 0x58
|
18
|
+
OP_9 = 0x59
|
19
|
+
OP_10 = 0x5a
|
20
|
+
OP_11 = 0x5b
|
21
|
+
OP_12 = 0x5c
|
22
|
+
OP_13 = 0x5d
|
23
|
+
OP_14 = 0x5e
|
24
|
+
OP_15 = 0x5f
|
25
|
+
OP_16 = 0x60
|
26
|
+
|
27
|
+
OP_PUSHDATA1 = 0x4c
|
28
|
+
OP_PUSHDATA2 = 0x4d
|
29
|
+
OP_PUSHDATA4 = 0x4e
|
30
|
+
OP_1NEGATE = 0x4f
|
31
|
+
|
32
|
+
# https://en.bitcoin.it/wiki/Script#Flow_control
|
33
|
+
OP_NOP = 0x61
|
34
|
+
OP_IF = 0x63
|
35
|
+
OP_NOTIF = 0x64
|
36
|
+
OP_ELSE = 0x67
|
37
|
+
OP_ENDIF = 0x68
|
38
|
+
OP_VERIFY = 0x69
|
39
|
+
OP_RETURN = 0x6a
|
40
|
+
|
41
|
+
# https://en.bitcoin.it/wiki/Script#Stack
|
42
|
+
OP_TOALTSTACK = 0x6b
|
43
|
+
OP_FROMALTSTACK = 0x6c
|
44
|
+
OP_IFDUP = 0x73
|
45
|
+
OP_DEPTH = 0x74
|
46
|
+
OP_DROP = 0x75
|
47
|
+
OP_DUP = 0x76
|
48
|
+
OP_NIP = 0x77
|
49
|
+
OP_OVER = 0x78
|
50
|
+
OP_PICK = 0x79
|
51
|
+
OP_ROLL = 0x7a
|
52
|
+
OP_ROT = 0x7b
|
53
|
+
OP_SWAP = 0x7c
|
54
|
+
OP_TUCK = 0x7d
|
55
|
+
OP_2DROP = 0x6d
|
56
|
+
OP_2DUP = 0x6e
|
57
|
+
OP_3DUP = 0x6f
|
58
|
+
OP_2OVER = 0x70
|
59
|
+
OP_2ROT = 0x71
|
60
|
+
OP_2SWAP = 0x72
|
61
|
+
|
62
|
+
# https://en.bitcoin.it/wiki/Script#Splice
|
63
|
+
OP_CAT = 0x7e # disabled
|
64
|
+
OP_SUBSTR = 0x7f # disabled
|
65
|
+
OP_LEFT = 0x80 # disabled
|
66
|
+
OP_RIGHT = 0x81 # disabled
|
67
|
+
OP_SIZE = 0x82
|
68
|
+
|
69
|
+
# https://en.bitcoin.it/wiki/Script#Bitwise_logic
|
70
|
+
OP_INVERT = 0x83 # disabled
|
71
|
+
OP_AND = 0x84 # disabled
|
72
|
+
OP_OR = 0x85 # disabled
|
73
|
+
OP_XOR = 0x86 # disabled
|
74
|
+
OP_EQUAL = 0x87
|
75
|
+
OP_EQUALVERIFY = 0x88
|
76
|
+
|
77
|
+
# https://en.bitcoin.it/wiki/Script#Arithmetic
|
78
|
+
OP_1ADD = 0x8b
|
79
|
+
OP_1SUB = 0x8c
|
80
|
+
OP_2MUL = 0x8d # disabled
|
81
|
+
OP_2DIV = 0x8e # disabled
|
82
|
+
OP_NEGATE = 0x8f
|
83
|
+
OP_ABS = 0x90
|
84
|
+
OP_NOT = 0x91
|
85
|
+
OP_0NOTEQUAL = 0x92
|
86
|
+
OP_ADD = 0x93
|
87
|
+
OP_SUB = 0x94
|
88
|
+
OP_MUL = 0x95 # disabled
|
89
|
+
OP_DIV = 0x96 # disabled
|
90
|
+
OP_MOD = 0x97 # disabled
|
91
|
+
OP_LSHIFT = 0x98 # disabled
|
92
|
+
OP_RSHIFT = 0x99 # disabled
|
93
|
+
OP_BOOLAND = 0x9a
|
94
|
+
OP_BOOLOR = 0x9b
|
95
|
+
OP_NUMEQUAL = 0x9c
|
96
|
+
OP_NUMEQUALVERIFY = 0x9d
|
97
|
+
OP_NUMNOTEQUAL = 0x9e
|
98
|
+
OP_LESSTHAN = 0x9f
|
99
|
+
OP_GREATERTHAN = 0xa0
|
100
|
+
OP_LESSTHANOREQUAL = 0xa1
|
101
|
+
OP_GREATERTHANOREQUAL = 0xa2
|
102
|
+
OP_MIN = 0xa3
|
103
|
+
OP_MAX = 0xa4
|
104
|
+
OP_WITHIN = 0xa5
|
105
|
+
|
106
|
+
# https://en.bitcoin.it/wiki/Script#Crypto
|
107
|
+
OP_RIPEMD160 = 0xa6
|
108
|
+
OP_SHA1 = 0xa7
|
109
|
+
OP_SHA256 = 0xa8
|
110
|
+
OP_HASH160 = 0xa9
|
111
|
+
OP_HASH256 = 0xaa
|
112
|
+
OP_CODESEPARATOR = 0xab
|
113
|
+
OP_CHECKSIG = 0xac
|
114
|
+
OP_CHECKSIGVERIFY= 0xad
|
115
|
+
OP_CHECKMULTISIG = 0xae
|
116
|
+
OP_CHECKMULTISIGVERIFY = 0xaf
|
117
|
+
|
118
|
+
# https://en.bitcoin.it/wiki/Script#Locktime
|
119
|
+
OP_NOP2 = OP_CHECKLOCKTIMEVERIFY = OP_CLTV = 0xb1
|
120
|
+
OP_NOP3 = OP_CHECKSEQUENCEVERIFY = OP_CSV = 0xb2
|
121
|
+
|
122
|
+
# https://en.bitcoin.it/wiki/Script#Reserved_words
|
123
|
+
OP_RESERVED = 0x50
|
124
|
+
OP_VER = 0x62
|
125
|
+
OP_VERIF = 0x65
|
126
|
+
OP_VERNOTIF = 0x66
|
127
|
+
OP_RESERVED1= 0x89
|
128
|
+
OP_RESERVED2 = 0x8a
|
129
|
+
|
130
|
+
OP_NOP1 = 0xb0
|
131
|
+
OP_NOP4 = 0xb3
|
132
|
+
OP_NOP5 = 0xb4
|
133
|
+
OP_NOP6 = 0xb5
|
134
|
+
OP_NOP7 = 0xb6
|
135
|
+
OP_NOP8 = 0xb7
|
136
|
+
OP_NOP9 = 0xb8
|
137
|
+
OP_NOP10 = 0xb9
|
138
|
+
|
139
|
+
# https://en.bitcoin.it/wiki/Script#Pseudo-words
|
140
|
+
OP_PUBKEYHASH = 0xfd
|
141
|
+
OP_PUBKEY = 0xfe
|
142
|
+
OP_INVALIDOPCODE = 0xff
|
143
|
+
|
144
|
+
DUPLICATE_KEY = [:OP_NOP2, :OP_NOP3]
|
145
|
+
OPCODES_MAP = Hash[*(constants.grep(/^OP_/) - [:OP_NOP2, :OP_NOP3, :OP_CHECKLOCKTIMEVERIFY, :OP_CHECKSEQUENCEVERIFY]).map { |c| [const_get(c), c.to_s] }.flatten]
|
146
|
+
NAME_MAP = Hash[*constants.grep(/^OP_/).map { |c| [c.to_s, const_get(c)] }.flatten]
|
147
|
+
|
148
|
+
def opcode_to_name(opcode)
|
149
|
+
return OPCODES_MAP[opcode].delete('OP_') if opcode == OP_0 || (opcode <= OP_16 && opcode >= OP_1)
|
150
|
+
OPCODES_MAP[opcode]
|
151
|
+
end
|
152
|
+
|
153
|
+
def name_to_opcode(name)
|
154
|
+
return NAME_MAP['OP_' + name] if name =~ /^\d/ && name.to_i < 17 && name.to_i > -1
|
155
|
+
NAME_MAP[name]
|
156
|
+
end
|
157
|
+
|
158
|
+
# whether opcode is predefined opcode
|
159
|
+
def defined?(opcode)
|
160
|
+
!opcode_to_name(opcode).nil?
|
161
|
+
end
|
162
|
+
|
163
|
+
def small_int_to_opcode(int)
|
164
|
+
return OP_0 if int == 0
|
165
|
+
return OP_1NEGATE if int == -1
|
166
|
+
return OP_1 + (int - 1) if int >= 1 && int <= 16
|
167
|
+
nil
|
168
|
+
end
|
169
|
+
|
170
|
+
def opcode_to_small_int(opcode)
|
171
|
+
return 0 if opcode == ''.b || opcode == OP_0
|
172
|
+
return -1 if opcode == OP_1NEGATE
|
173
|
+
return opcode - (OP_1 - 1) if opcode >= OP_1 && opcode <= OP_16
|
174
|
+
nil
|
175
|
+
end
|
176
|
+
|
177
|
+
end
|
178
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Tapyrus
|
2
|
+
|
3
|
+
# outpoint class
|
4
|
+
class OutPoint
|
5
|
+
|
6
|
+
COINBASE_HASH = '0000000000000000000000000000000000000000000000000000000000000000'
|
7
|
+
COINBASE_INDEX = 4294967295
|
8
|
+
|
9
|
+
attr_reader :tx_hash
|
10
|
+
attr_reader :index
|
11
|
+
|
12
|
+
def initialize(tx_hash, index = -1)
|
13
|
+
@tx_hash = tx_hash
|
14
|
+
@index = index
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.from_txid(txid, index)
|
18
|
+
self.new(txid.rhex, index)
|
19
|
+
end
|
20
|
+
|
21
|
+
def coinbase?
|
22
|
+
tx_hash == COINBASE_HASH && index == COINBASE_INDEX
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_payload
|
26
|
+
[tx_hash.htb, index].pack('a32V')
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.create_coinbase_outpoint
|
30
|
+
new(COINBASE_HASH, COINBASE_INDEX)
|
31
|
+
end
|
32
|
+
|
33
|
+
def valid?
|
34
|
+
index >= 0 && (!coinbase? && tx_hash != COINBASE_HASH)
|
35
|
+
end
|
36
|
+
|
37
|
+
# convert hash to txid
|
38
|
+
def txid
|
39
|
+
tx_hash.rhex
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'evma_httpserver'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module Tapyrus
|
5
|
+
module RPC
|
6
|
+
|
7
|
+
# Tapyrusrb RPC server.
|
8
|
+
class HttpServer < EM::Connection
|
9
|
+
include EM::HttpServer
|
10
|
+
include RequestHandler
|
11
|
+
|
12
|
+
attr_reader :node
|
13
|
+
attr_accessor :logger
|
14
|
+
|
15
|
+
def initialize(node)
|
16
|
+
@node = node
|
17
|
+
@logger = Tapyrus::Logger.create(:debug)
|
18
|
+
end
|
19
|
+
|
20
|
+
def post_init
|
21
|
+
super
|
22
|
+
logger.debug 'start http server.'
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.run(node, port = 8332)
|
26
|
+
EM.start_server('0.0.0.0', port, HttpServer, node)
|
27
|
+
end
|
28
|
+
|
29
|
+
# process http request.
|
30
|
+
def process_http_request
|
31
|
+
operation = proc {
|
32
|
+
command, args = parse_json_params
|
33
|
+
logger.debug("process http request. command = #{command}")
|
34
|
+
begin
|
35
|
+
send(command, *args).to_json
|
36
|
+
rescue Exception => e
|
37
|
+
e
|
38
|
+
end
|
39
|
+
}
|
40
|
+
callback = proc{ |result|
|
41
|
+
response = EM::DelegatedHttpResponse.new(self)
|
42
|
+
if result.is_a?(Exception)
|
43
|
+
response.status = 500
|
44
|
+
response.content = result.message
|
45
|
+
else
|
46
|
+
response.status = 200
|
47
|
+
response.content = result
|
48
|
+
end
|
49
|
+
response.content_type 'application/json'
|
50
|
+
response.send_response
|
51
|
+
}
|
52
|
+
EM.defer(operation, callback)
|
53
|
+
end
|
54
|
+
|
55
|
+
# parse request parameter.
|
56
|
+
# @return [Array] the array of command and args
|
57
|
+
def parse_json_params
|
58
|
+
params = JSON.parse(@http_post_content)
|
59
|
+
[params['method'], params['params']]
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|