bitcoinrb 0.1.1 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/bitcoinrb.gemspec +7 -3
- data/exe/bitcoinrb-cli +1 -1
- data/exe/bitcoinrbd +9 -23
- data/lib/bitcoin/block.rb +66 -0
- data/lib/bitcoin/block_header.rb +33 -1
- data/lib/bitcoin/chain_params.rb +11 -4
- data/lib/bitcoin/chainparams/mainnet.yml +9 -2
- data/lib/bitcoin/chainparams/regtest.yml +9 -1
- data/lib/bitcoin/chainparams/testnet.yml +9 -1
- data/lib/bitcoin/constants.rb +13 -0
- data/lib/bitcoin/ext_key.rb +2 -1
- data/lib/bitcoin/merkle_tree.rb +50 -51
- data/lib/bitcoin/message/block.rb +5 -0
- data/lib/bitcoin/message.rb +5 -5
- data/lib/bitcoin/network/connection.rb +58 -0
- data/lib/bitcoin/network/message_handler.rb +192 -0
- data/lib/bitcoin/network/peer.rb +55 -0
- data/lib/bitcoin/network/peer_discovery.rb +34 -0
- data/lib/bitcoin/network/pool.rb +74 -0
- data/lib/bitcoin/network.rb +14 -0
- data/lib/bitcoin/node/spv.rb +39 -0
- data/lib/bitcoin/node/spv_block_chain.rb +15 -0
- data/lib/bitcoin/node.rb +6 -0
- data/lib/bitcoin/script/script.rb +13 -0
- data/lib/bitcoin/store/spv_block_store.rb +11 -0
- data/lib/bitcoin/store.rb +7 -0
- data/lib/bitcoin/tx.rb +11 -0
- data/lib/bitcoin/tx_out.rb +2 -0
- data/lib/bitcoin/validation.rb +33 -11
- data/lib/bitcoin/version.rb +1 -1
- data/lib/bitcoin.rb +5 -2
- data/lib/openassets/marker_output.rb +14 -0
- data/lib/openassets/payload.rb +45 -0
- data/lib/openassets.rb +8 -0
- metadata +59 -8
- data/lib/bitcoin/connection.rb +0 -68
- data/lib/bitcoin/message/handler.rb +0 -183
- data/lib/bitcoin/nodes/spv/cli.rb +0 -12
- data/lib/bitcoin/nodes/spv/daemon.rb +0 -21
- data/lib/bitcoin/nodes/spv.rb +0 -13
- data/lib/bitcoin/nodes.rb +0 -5
@@ -0,0 +1,192 @@
|
|
1
|
+
module Bitcoin
|
2
|
+
module Network
|
3
|
+
|
4
|
+
# P2P message handler used by peer connection class.
|
5
|
+
module MessageHandler
|
6
|
+
|
7
|
+
# handle p2p message.
|
8
|
+
def handle(message)
|
9
|
+
logger.info "handle message #{message.bth}"
|
10
|
+
begin
|
11
|
+
parse(message)
|
12
|
+
rescue Bitcoin::Message::Error => e
|
13
|
+
logger.error("invalid header magic. #{e.message}")
|
14
|
+
close
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def parse(message)
|
19
|
+
@message += message
|
20
|
+
command, payload, rest = parse_header
|
21
|
+
return unless command
|
22
|
+
|
23
|
+
handle_command(command, payload)
|
24
|
+
@message = ""
|
25
|
+
parse(rest) if rest && rest.bytesize > 0
|
26
|
+
end
|
27
|
+
|
28
|
+
def parse_header
|
29
|
+
head_magic = Bitcoin.chain_params.magic_head
|
30
|
+
return if @message.nil? || @message.size < MESSAGE_HEADER_SIZE
|
31
|
+
|
32
|
+
magic, command, length, checksum = @message.unpack('a4A12Va4')
|
33
|
+
raise Bitcoin::Message::Error, "invalid header magic. #{magic.bth}" unless magic.bth == head_magic
|
34
|
+
|
35
|
+
payload = @message[MESSAGE_HEADER_SIZE...(MESSAGE_HEADER_SIZE + length)]
|
36
|
+
return if payload.size < length
|
37
|
+
raise Bitcoin::Message::Error, "header checksum mismatch. #{checksum.bth}" unless Bitcoin.double_sha256(payload)[0...4] == checksum
|
38
|
+
|
39
|
+
rest = @message[(MESSAGE_HEADER_SIZE + length)..-1]
|
40
|
+
[command, payload, rest]
|
41
|
+
end
|
42
|
+
|
43
|
+
def handle_command(command, payload)
|
44
|
+
logger.info("[#{addr}] process command #{command}. payload = #{payload.bth}")
|
45
|
+
case command
|
46
|
+
when Bitcoin::Message::Version::COMMAND
|
47
|
+
on_version(Bitcoin::Message::Version.parse_from_payload(payload))
|
48
|
+
when Bitcoin::Message::VerAck::COMMAND
|
49
|
+
on_ver_ack
|
50
|
+
when Bitcoin::Message::GetAddr::COMMAND
|
51
|
+
on_get_addr
|
52
|
+
when Bitcoin::Message::Addr::COMMAND
|
53
|
+
on_addr(Bitcoin::Message::Addr.parse_from_payload(payload))
|
54
|
+
when Bitcoin::Message::SendHeaders::COMMAND
|
55
|
+
on_send_headers
|
56
|
+
when Bitcoin::Message::FeeFilter::COMMAND
|
57
|
+
on_fee_filter(Bitcoin::Message::FeeFilter.parse_from_payload(payload))
|
58
|
+
when Bitcoin::Message::Ping::COMMAND
|
59
|
+
on_ping(Bitcoin::Message::Ping.parse_from_payload(payload))
|
60
|
+
when Bitcoin::Message::Pong::COMMAND
|
61
|
+
on_pong(Bitcoin::Message::Pong.parse_from_payload(payload))
|
62
|
+
when Bitcoin::Message::GetHeaders::COMMAND
|
63
|
+
on_get_headers(Bitcoin::Message::GetHeaders.parse_from_payload(payload))
|
64
|
+
when Bitcoin::Message::Headers::COMMAND
|
65
|
+
on_headers(Bitcoin::Message::Headers.parse_from_payload(payload))
|
66
|
+
when Bitcoin::Message::Block::COMMAND
|
67
|
+
on_block(Bitcoin::Message::Block.parse_from_payload(payload))
|
68
|
+
when Bitcoin::Message::Tx::COMMAND
|
69
|
+
on_tx(Bitcoin::Message::Tx.parse_from_payload(payload))
|
70
|
+
when Bitcoin::Message::NotFound::COMMAND
|
71
|
+
on_not_found(Bitcoin::Message::NotFound.parse_from_payload(payload))
|
72
|
+
when Bitcoin::Message::MemPool::COMMAND
|
73
|
+
on_mem_pool
|
74
|
+
when Bitcoin::Message::Reject::COMMAND
|
75
|
+
on_reject(Bitcoin::Message::Reject.parse_from_payload(payload))
|
76
|
+
when Bitcoin::Message::SendCmpct::COMMAND
|
77
|
+
on_send_cmpct(Bitcoin::Message::SendCmpct.parse_from_payload(payload))
|
78
|
+
when Bitcoin::Message::Inv::COMMAND
|
79
|
+
on_inv(Bitcoin::Message::Inv.parse_from_payload(payload))
|
80
|
+
else
|
81
|
+
logger.warn("unsupported command received. #{command}")
|
82
|
+
close("with command #{command}")
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def send_message(msg)
|
87
|
+
logger.info "send message #{msg.class::COMMAND}"
|
88
|
+
send_data(msg.to_pkt)
|
89
|
+
end
|
90
|
+
|
91
|
+
# start handshake
|
92
|
+
def begin_handshake
|
93
|
+
logger.info "begin handshake with #{addr}"
|
94
|
+
ver = Bitcoin::Message::Version.new(remote_addr: addr, start_height: 0) # TODO use start_height in wallet
|
95
|
+
send_message(ver)
|
96
|
+
end
|
97
|
+
|
98
|
+
def handshake_done
|
99
|
+
logger.info 'handshake finished.'
|
100
|
+
@connected = true
|
101
|
+
post_handshake
|
102
|
+
end
|
103
|
+
|
104
|
+
def on_version(version)
|
105
|
+
logger.info("receive version message. #{version.build_json}")
|
106
|
+
@version = version
|
107
|
+
send_message(Bitcoin::Message::VerAck.new)
|
108
|
+
end
|
109
|
+
|
110
|
+
def on_ver_ack
|
111
|
+
logger.info('receive verack message.')
|
112
|
+
handshake_done
|
113
|
+
end
|
114
|
+
|
115
|
+
def on_get_addr
|
116
|
+
logger.info('receive getaddr message.')
|
117
|
+
end
|
118
|
+
|
119
|
+
def on_addr(addr)
|
120
|
+
logger.info("receive addr message. #{addr.build_json}")
|
121
|
+
# TODO
|
122
|
+
end
|
123
|
+
|
124
|
+
def on_send_headers
|
125
|
+
logger.info('receive sendheaders message.')
|
126
|
+
@sendheaders = true
|
127
|
+
end
|
128
|
+
|
129
|
+
def on_fee_filter(fee_filter)
|
130
|
+
logger.info("receive feefilter message. #{fee_filter.build_json}")
|
131
|
+
@fee_rate = fee_filter.fee_rate
|
132
|
+
end
|
133
|
+
|
134
|
+
def on_ping(ping)
|
135
|
+
logger.info("receive ping message. #{ping.build_json}")
|
136
|
+
send_message(ping.to_response)
|
137
|
+
end
|
138
|
+
|
139
|
+
def on_pong(pong)
|
140
|
+
logger.info("receive pong message. #{pong.build_json}")
|
141
|
+
# TODO calculate response
|
142
|
+
end
|
143
|
+
|
144
|
+
def on_get_headers(headers)
|
145
|
+
logger.info("receive getheaders message. #{headers.build_json}")
|
146
|
+
# TODO
|
147
|
+
end
|
148
|
+
|
149
|
+
def on_headers(headers)
|
150
|
+
logger.info("receive headers message. #{headers.build_json}")
|
151
|
+
# TODO
|
152
|
+
end
|
153
|
+
|
154
|
+
def on_block(block)
|
155
|
+
logger.info("receive block message. #{block.build_json}")
|
156
|
+
# TODO
|
157
|
+
end
|
158
|
+
|
159
|
+
def on_tx(tx)
|
160
|
+
logger.info("receive tx message. #{tx.build_json}")
|
161
|
+
# TODO
|
162
|
+
end
|
163
|
+
|
164
|
+
def on_not_found(not_found)
|
165
|
+
logger.info("receive notfound message. #{not_found.build_json}")
|
166
|
+
# TODO
|
167
|
+
end
|
168
|
+
|
169
|
+
def on_mem_pool
|
170
|
+
logger.info('receive mempool message.')
|
171
|
+
# TODO return mempool tx
|
172
|
+
end
|
173
|
+
|
174
|
+
def on_reject(reject)
|
175
|
+
logger.warn("receive reject message. #{reject.build_json}")
|
176
|
+
# TODO
|
177
|
+
end
|
178
|
+
|
179
|
+
def on_send_cmpct(cmpct)
|
180
|
+
logger.info("receive sendcmpct message. #{cmpct.build_json}")
|
181
|
+
# TODO if mode is high and version is 1, relay block with cmpctblock message
|
182
|
+
end
|
183
|
+
|
184
|
+
def on_inv(inv)
|
185
|
+
logger.info("receive inv message. #{inv.build_json}")
|
186
|
+
# TODO
|
187
|
+
end
|
188
|
+
|
189
|
+
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Bitcoin
|
2
|
+
module Network
|
3
|
+
|
4
|
+
# remote peer class.
|
5
|
+
class Peer
|
6
|
+
|
7
|
+
# remote peer info
|
8
|
+
attr_reader :host
|
9
|
+
attr_reader :port
|
10
|
+
# remote peer connection
|
11
|
+
attr_accessor :conn
|
12
|
+
attr_accessor :connected
|
13
|
+
# parent pool
|
14
|
+
attr_reader :pool
|
15
|
+
attr_accessor :fee_rate
|
16
|
+
|
17
|
+
def initialize(host, port, pool)
|
18
|
+
@host = host
|
19
|
+
@port = port
|
20
|
+
@pool = pool
|
21
|
+
@connected = false
|
22
|
+
end
|
23
|
+
|
24
|
+
def connect
|
25
|
+
self.conn ||= EM.connect(host, port, Bitcoin::Network::Connection, self)
|
26
|
+
end
|
27
|
+
|
28
|
+
def connected?
|
29
|
+
@connected
|
30
|
+
end
|
31
|
+
|
32
|
+
def addr
|
33
|
+
"#{host}:#{port}"
|
34
|
+
end
|
35
|
+
|
36
|
+
def post_handshake
|
37
|
+
@connected = true
|
38
|
+
pool.handle_new_peer(self)
|
39
|
+
end
|
40
|
+
|
41
|
+
# broadcast tx.
|
42
|
+
def broadcast_tx(tx)
|
43
|
+
send_message(Bitcoin::Message::Tx.new(tx, ))
|
44
|
+
end
|
45
|
+
|
46
|
+
# check the remote peer support segwit.
|
47
|
+
def support_segwit?
|
48
|
+
return false unless conn.version
|
49
|
+
conn.version.services & Bitcoin::Message::SERVICE_FLAGS[:witness] > 0
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Bitcoin
|
2
|
+
module Network
|
3
|
+
|
4
|
+
class PeerDiscovery
|
5
|
+
|
6
|
+
attr_reader :logger
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@logger = Bitcoin::Logger.create(:debug)
|
10
|
+
end
|
11
|
+
|
12
|
+
# get peer addresses, from DNS seeds.
|
13
|
+
def peers
|
14
|
+
# TODO add find from previous connected peer at first.
|
15
|
+
find_from_dns_seeds
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def find_from_dns_seeds
|
21
|
+
logger.debug 'discover peer address from DNS seeds.'
|
22
|
+
Bitcoin.chain_params.dns_seeds.map {|seed|
|
23
|
+
begin
|
24
|
+
Socket.getaddrinfo(seed, Bitcoin.chain_params.default_port).map{|a|a[2]}.uniq
|
25
|
+
rescue SocketError => e
|
26
|
+
logger.error "SocketError occurred when load DNS seed: #{seed}, error: #{e.message}"
|
27
|
+
end
|
28
|
+
}.flatten
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module Bitcoin
|
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
|
+
|
15
|
+
attr_reader :peers # active peers
|
16
|
+
attr_reader :pending_peers # currently connecting peer
|
17
|
+
attr_reader :chain
|
18
|
+
attr_reader :max_outbound
|
19
|
+
attr_reader :logger
|
20
|
+
attr_reader :peer_discovery
|
21
|
+
attr_accessor :started
|
22
|
+
|
23
|
+
def initialize
|
24
|
+
@peers = []
|
25
|
+
@pending_peers = []
|
26
|
+
@max_outbound = MAX_OUTBOUND_CONNECTIONS
|
27
|
+
@chain = Bitcoin::Node::SPVBlockChain.new
|
28
|
+
@logger = Bitcoin::Logger.create(:debug)
|
29
|
+
@peer_discovery = PeerDiscovery.new
|
30
|
+
@started = false
|
31
|
+
end
|
32
|
+
|
33
|
+
# connecting other peers and begin network activity.
|
34
|
+
def start
|
35
|
+
raise 'Cannot start a peer pool twice.' if started
|
36
|
+
logger.debug 'Start connecting other pears.'
|
37
|
+
addr_list = peer_discovery.peers
|
38
|
+
port = Bitcoin.chain_params.default_port
|
39
|
+
Parallel.map(addr_list, in_processes: 3) do |ip|
|
40
|
+
if peers.size < MAX_OUTBOUND_CONNECTIONS
|
41
|
+
EM.run do
|
42
|
+
peer = Peer.new(ip, port, self)
|
43
|
+
pending_peers << peer
|
44
|
+
peer.connect
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
@started = true
|
49
|
+
end
|
50
|
+
|
51
|
+
# detect new peer connection.
|
52
|
+
def handle_new_peer(peer)
|
53
|
+
logger.debug "connected new peer #{peer.addr}"
|
54
|
+
peers << peer
|
55
|
+
pending_peers.delete(peer)
|
56
|
+
end
|
57
|
+
|
58
|
+
# terminate peers.
|
59
|
+
def terminate
|
60
|
+
peers.each {|peer| peer.close('terminate')}
|
61
|
+
@peers = []
|
62
|
+
@started = false
|
63
|
+
end
|
64
|
+
|
65
|
+
def broadcast(tx)
|
66
|
+
peers.each {|peer| peer.broadcast(tx) }
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'parallel'
|
2
|
+
require 'eventmachine'
|
3
|
+
|
4
|
+
module Bitcoin
|
5
|
+
module Network
|
6
|
+
|
7
|
+
autoload :MessageHandler, 'bitcoin/network/message_handler'
|
8
|
+
autoload :Connection, 'bitcoin/network/connection'
|
9
|
+
autoload :Pool, 'bitcoin/network/pool'
|
10
|
+
autoload :Peer, 'bitcoin/network/peer'
|
11
|
+
autoload :PeerDiscovery, 'bitcoin/network/peer_discovery'
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Bitcoin
|
2
|
+
module Node
|
3
|
+
|
4
|
+
# SPV class
|
5
|
+
class SPV
|
6
|
+
|
7
|
+
attr_reader :pool
|
8
|
+
attr_reader :logger
|
9
|
+
attr_accessor :running
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@pool = Bitcoin::Network::Pool.new
|
13
|
+
@logger = Bitcoin::Logger.create(:debug)
|
14
|
+
@running = false
|
15
|
+
end
|
16
|
+
|
17
|
+
# open the node.
|
18
|
+
def run
|
19
|
+
return if running
|
20
|
+
logger.debug 'SPV node start running.'
|
21
|
+
pool.start
|
22
|
+
end
|
23
|
+
|
24
|
+
# close the node.
|
25
|
+
def shutdown
|
26
|
+
pool.terminate
|
27
|
+
logger.debug 'SPV node shutdown.'
|
28
|
+
end
|
29
|
+
|
30
|
+
# broadcast a transaction
|
31
|
+
def broadcast(tx)
|
32
|
+
pool.broadcast(tx)
|
33
|
+
logger.debug "broadcast tx: #{tx.to_payload.bth}"
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
data/lib/bitcoin/node.rb
ADDED
@@ -159,6 +159,11 @@ module Bitcoin
|
|
159
159
|
(chunks.size == 1 || chunks[1].opcode <= OP_16)
|
160
160
|
end
|
161
161
|
|
162
|
+
def op_return_data
|
163
|
+
return nil unless op_return?
|
164
|
+
chunks[1].pushed_data
|
165
|
+
end
|
166
|
+
|
162
167
|
# whether data push only script which dose not include other opcode
|
163
168
|
def push_only?
|
164
169
|
chunks.each do |c|
|
@@ -184,6 +189,14 @@ module Bitcoin
|
|
184
189
|
false
|
185
190
|
end
|
186
191
|
|
192
|
+
# get witness commitment
|
193
|
+
def witness_commitment
|
194
|
+
return nil if !op_return? || op_return_data.bytesize < 36
|
195
|
+
buf = StringIO.new(op_return_data)
|
196
|
+
return nil unless buf.read(4).bth == WITNESS_COMMITMENT_HEADER
|
197
|
+
buf.read(32).bth
|
198
|
+
end
|
199
|
+
|
187
200
|
# If this script is witness program, return its script code,
|
188
201
|
# otherwise returns the self payload. ScriptInterpreter does not use this.
|
189
202
|
def to_script_code(skip_separator_index = 0)
|
data/lib/bitcoin/tx.rb
CHANGED
@@ -71,6 +71,17 @@ module Bitcoin
|
|
71
71
|
Bitcoin.double_sha256(to_payload).reverse.bth
|
72
72
|
end
|
73
73
|
|
74
|
+
# get the witness commitment of coinbase tx.
|
75
|
+
# if this tx does not coinbase or not have commitment, return nil.
|
76
|
+
def witness_commitment
|
77
|
+
return nil unless coinbase_tx?
|
78
|
+
outputs.each do |output|
|
79
|
+
commitment = output.script_pubkey.witness_commitment
|
80
|
+
return commitment if commitment
|
81
|
+
end
|
82
|
+
nil
|
83
|
+
end
|
84
|
+
|
74
85
|
def to_payload
|
75
86
|
witness? ? serialize_witness_format : serialize_old_format
|
76
87
|
end
|
data/lib/bitcoin/tx_out.rb
CHANGED
data/lib/bitcoin/validation.rb
CHANGED
@@ -6,47 +6,69 @@ module Bitcoin
|
|
6
6
|
def check_tx(tx, state)
|
7
7
|
# Basic checks that don't depend on any context
|
8
8
|
if tx.inputs.empty?
|
9
|
-
return state.DoS(10, reject_code: Message::Reject::CODE_INVALID,
|
9
|
+
return state.DoS(10, reject_code: Message::Reject::CODE_INVALID, reject_reason: 'bad-txns-vin-empty')
|
10
10
|
end
|
11
11
|
|
12
12
|
if tx.outputs.empty?
|
13
|
-
return state.DoS(100, reject_code: Message::Reject::CODE_INVALID,
|
13
|
+
return state.DoS(100, reject_code: Message::Reject::CODE_INVALID, reject_reason: 'bad-txns-vout-empty')
|
14
14
|
end
|
15
15
|
|
16
16
|
# Size limits (this doesn't take the witness into account, as that hasn't been checked for malleability)
|
17
17
|
if tx.serialize_old_format.bytesize * Bitcoin::WITNESS_SCALE_FACTOR > Bitcoin::MAX_BLOCK_WEIGHT
|
18
|
-
return state.DoS(100, reject_code: Message::Reject::CODE_INVALID,
|
18
|
+
return state.DoS(100, reject_code: Message::Reject::CODE_INVALID, reject_reason: 'bad-txns-oversize')
|
19
19
|
end
|
20
20
|
|
21
21
|
# Check for negative or overflow output values
|
22
22
|
amount = 0
|
23
23
|
tx.outputs.each do |o|
|
24
|
-
return state.DoS(100, reject_code: Message::Reject::CODE_INVALID,
|
25
|
-
return state.DoS(100, reject_code: Message::Reject::CODE_INVALID,
|
24
|
+
return state.DoS(100, reject_code: Message::Reject::CODE_INVALID, reject_reason: 'bad-txns-vout-negative') if o.value < 0
|
25
|
+
return state.DoS(100, reject_code: Message::Reject::CODE_INVALID, reject_reason: 'bad-txns-vout-toolarge') if MAX_MONEY < o.value
|
26
26
|
amount += o.value
|
27
|
-
return state.DoS(100, reject_code: Message::Reject::CODE_INVALID,
|
27
|
+
return state.DoS(100, reject_code: Message::Reject::CODE_INVALID, reject_reason: 'bad-txns-vout-toolarge') if MAX_MONEY < amount
|
28
28
|
end
|
29
29
|
|
30
30
|
# Check for duplicate inputs - note that this check is slow so we skip it in CheckBlock
|
31
31
|
out_points = tx.inputs.map{|i|i.out_point.to_payload}
|
32
32
|
unless out_points.size == out_points.uniq.size
|
33
|
-
return state.DoS(100, reject_code: Message::Reject::CODE_INVALID,
|
33
|
+
return state.DoS(100, reject_code: Message::Reject::CODE_INVALID, reject_reason: 'bad-txns-inputs-duplicate')
|
34
34
|
end
|
35
35
|
|
36
36
|
if tx.coinbase_tx?
|
37
37
|
if tx.inputs[0].script_sig.size < 2 || tx.inputs[0].script_sig.size > 100
|
38
|
-
return state.DoS(100, reject_code: Message::Reject::CODE_INVALID,
|
38
|
+
return state.DoS(100, reject_code: Message::Reject::CODE_INVALID, reject_reason: 'bad-cb-length')
|
39
39
|
end
|
40
40
|
else
|
41
41
|
tx.inputs.each do |i|
|
42
42
|
if i.out_point.nil? || !i.out_point.valid?
|
43
|
-
return state.DoS(10, reject_code: Message::Reject::CODE_INVALID,
|
43
|
+
return state.DoS(10, reject_code: Message::Reject::CODE_INVALID, reject_reason: 'bad-txns-prevout-null')
|
44
44
|
end
|
45
45
|
end
|
46
46
|
end
|
47
47
|
true
|
48
48
|
end
|
49
49
|
|
50
|
+
# check proof of work
|
51
|
+
def check_block_header(header, state)
|
52
|
+
header.hash
|
53
|
+
header.bits
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
def check_block(block, state)
|
58
|
+
# check block header
|
59
|
+
return false unless check_block_header(block.header, state)
|
60
|
+
|
61
|
+
# check merkle root
|
62
|
+
|
63
|
+
# size limits
|
64
|
+
|
65
|
+
# first tx is coinbase?
|
66
|
+
|
67
|
+
# check tx count
|
68
|
+
|
69
|
+
# check sigop count
|
70
|
+
end
|
71
|
+
|
50
72
|
end
|
51
73
|
|
52
74
|
class ValidationState
|
@@ -67,9 +89,9 @@ module Bitcoin
|
|
67
89
|
@corruption_possible = false
|
68
90
|
end
|
69
91
|
|
70
|
-
def DoS(level, ret: false, reject_code: 0,
|
92
|
+
def DoS(level, ret: false, reject_code: 0, reject_reason: '', corruption_in: false, debug_message: '')
|
71
93
|
@reject_code = reject_code
|
72
|
-
@reject_reason =
|
94
|
+
@reject_reason = reject_reason
|
73
95
|
@corruption_possible = corruption_in
|
74
96
|
@debug_message = debug_message
|
75
97
|
return ret if mode == MODE[:error]
|
data/lib/bitcoin/version.rb
CHANGED
data/lib/bitcoin.rb
CHANGED
@@ -5,14 +5,15 @@ require 'securerandom'
|
|
5
5
|
require 'json'
|
6
6
|
require 'bech32'
|
7
7
|
require 'ffi'
|
8
|
+
require_relative 'openassets'
|
8
9
|
|
9
10
|
module Bitcoin
|
10
11
|
|
11
12
|
autoload :Util, 'bitcoin/util'
|
12
13
|
autoload :ChainParams, 'bitcoin/chain_params'
|
13
14
|
autoload :Message, 'bitcoin/message'
|
14
|
-
autoload :Connection, 'bitcoin/connection'
|
15
15
|
autoload :Logger, 'bitcoin/logger'
|
16
|
+
autoload :Block, 'bitcoin/block'
|
16
17
|
autoload :BlockHeader, 'bitcoin/block_header'
|
17
18
|
autoload :Tx, 'bitcoin/tx'
|
18
19
|
autoload :Script, 'bitcoin/script/script'
|
@@ -27,11 +28,13 @@ module Bitcoin
|
|
27
28
|
autoload :Key, 'bitcoin/key'
|
28
29
|
autoload :ExtKey, 'bitcoin/ext_key'
|
29
30
|
autoload :Opcodes, 'bitcoin/opcodes'
|
30
|
-
autoload :Node, 'bitcoin/
|
31
|
+
autoload :Node, 'bitcoin/node'
|
31
32
|
autoload :Base58, 'bitcoin/base58'
|
32
33
|
autoload :Secp256k1, 'bitcoin/secp256k1'
|
33
34
|
autoload :Mnemonic, 'bitcoin/mnemonic'
|
34
35
|
autoload :ValidationState, 'bitcoin/validation'
|
36
|
+
autoload :Network, 'bitcoin/network'
|
37
|
+
autoload :Store, 'bitcoin/store'
|
35
38
|
|
36
39
|
require_relative 'bitcoin/constants'
|
37
40
|
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module OpenAssets
|
2
|
+
|
3
|
+
module MarkerOutput
|
4
|
+
|
5
|
+
# whether this output is marker output for open assets.
|
6
|
+
def open_assets_marker?
|
7
|
+
return false unless script_pubkey.op_return?
|
8
|
+
payload = Payload.parse_from_payload(script_pubkey.op_return_data)
|
9
|
+
!payload.nil?
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|