spec/bitcoin/storage/validation_spec.rb in bitcoin-ruby-0.0.5 vs spec/bitcoin/storage/validation_spec.rb in bitcoin-ruby-0.0.6
- old
+ new
@@ -65,18 +65,20 @@
end
it "4. Block hash must satisfy claimed nBits proof of work" do
@block.bits = Bitcoin.encode_compact_bits("0000#{"ff" * 30}")
@block.recalc_block_hash
- target = Bitcoin.decode_compact_bits(block.bits).to_i(16)
+ target = Bitcoin.decode_compact_bits(@block.bits).to_i(16)
check_block(@block, [:bits, [@block.hash.to_i(16), target]])
end
it "5. Block timestamp must not be more than two hours in the future" do
- fake_time = (Time.now + 3 * 60 * 60).to_i
- check_block(@block, [:max_timestamp, [fake_time, Time.now.to_i + 7200]]) {|b|
- b.time = fake_time }
+ Time.freeze do
+ fake_time = (Time.now + 3 * 60 * 60).to_i
+ check_block(@block, [:max_timestamp, [fake_time, Time.now.to_i + 7200]]) {|b|
+ b.time = fake_time }
+ end
end
it "6. First transaction must be coinbase (i.e. only 1 input, with hash=0, n=-1), the rest must not be" do
block = create_block @block1.hash, false, [
->(tx) { create_tx(tx, @block1.tx.first, 0, [[50, @key]]) } ], @key
@@ -106,24 +108,31 @@
it "13. Reject if timestamp is the median time of the last 11 blocks or before" do
prev_block = @block1
12.times do |i|
prev_block = create_block(prev_block.hash, false, [])
- prev_block.time = Time.now.to_i - (12-i)
+ prev_block.time = i
prev_block.recalc_block_hash
@store.store_block(prev_block).should == [i+2, 0]
end
+
block = create_block(prev_block.hash, false, [], @key)
- fake_time = Time.now.to_i - 100
+ fake_time = @store.get_block_by_depth(8).time - 1
times = @store.db[:blk].where("depth > 2").map{|b|b[:time]}.sort
m, r = times.size.divmod(2)
min_time = (r == 0 ? times[m-1, 2].inject(:+) / 2.0 : times[m])
+
+ # reject before median time
check_block(block, [:min_timestamp, [fake_time, min_time]]) {|b| b.time = fake_time }
+ # reject at exactly median time
fake_time = @store.get_block_by_depth(8).time
check_block(block, [:min_timestamp, [fake_time, fake_time]]) {|b| b.time = fake_time }
+
+ # accept after median time
+ block.time = @store.get_block_by_depth(8).time + 1; block.recalc_block_hash
@store.store_block(block).should == [14, 0]
end
it "should allow chains of unconfirmed transactions" do
tx1 = build_tx {|t| create_tx(t, @block1.tx.first, 0, [[50, @key]]) }
@@ -143,17 +152,17 @@
@store.store_block(block2).should == [2, 0]
block3 = create_block(block2.hash, false, [], @key, 60e8)
-> { @store.store_block(block3) }.should.raise(ValidationError)
- Bitcoin::Validation::REWARD_DROP = 2
+ Bitcoin::REWARD_DROP = 2
block4 = create_block(block2.hash, false, [], @key, 50e8)
-> { @store.store_block(block4) }.should.raise(ValidationError)
block5 = create_block(block2.hash, false, [], @key, 25e8)
@store.store_block(block5).should == [3, 0]
- Bitcoin::Validation::REWARD_DROP = 210_000
+ Bitcoin::REWARD_DROP = 210_000
end
end
describe "transaction rules (#{options[0]} - #{options[1]})" do
@@ -205,14 +214,14 @@
check_tx(@tx, [:lists, [0, 1]]) {|tx| tx.instance_eval { @in = [] } }
check_tx(@tx, [:lists, [1, 0]]) {|tx| tx.instance_eval { @out = [] } }
end
it "3. Size in bytes < MAX_BLOCK_SIZE" do
- max = Bitcoin::Validation::MAX_BLOCK_SIZE; Bitcoin::Validation::MAX_BLOCK_SIZE = 1000
+ max = Bitcoin::MAX_BLOCK_SIZE; Bitcoin::MAX_BLOCK_SIZE = 1000
check_tx(@tx, [:max_size, [@tx.payload.bytesize+978, 1000]]) {|tx|
tx.out[0].pk_script = "\x00" * 1001 }
- Bitcoin::Validation::MAX_BLOCK_SIZE = max
+ Bitcoin::MAX_BLOCK_SIZE = max
end
it "4. Each output value, as well as the total, must be in legal money range" do
check_tx(@tx, [:output_values, [Bitcoin::network[:max_money] + 1, Bitcoin::network[:max_money]]]) {|tx|
tx.out[0].value = Bitcoin::network[:max_money] + 1 }
@@ -223,12 +232,12 @@
tx.in.first.prev_out = "\x00"*32
tx.in.first.prev_out_index = 4294967295
end
end
- it "6. Check that nLockTime <= INT_MAX, size in bytes >= 100, and sig opcount <= 2" do
- check_tx(@tx, [:lock_time, [INT_MAX + 1, INT_MAX]]) {|tx| tx.lock_time = INT_MAX + 1 }
+ it "6. Check that nLockTime <= UINT32_MAX, size in bytes >= 100, and sig opcount <= 2" do
+ check_tx(@tx, [:lock_time, [Bitcoin::UINT32_MAX + 1, Bitcoin::UINT32_MAX]]) {|tx| tx.lock_time = Bitcoin::UINT32_MAX + 1 }
# TODO: validate sig opcount
end
# it "7. Reject 'nonstandard' transactions: scriptSig doing anything other than pushing numbers on the stack, or scriptPubkey not matching the two usual forms" do
# check_tx(@tx, /standard/) {|tx| tx.out[0].pk_script = Bitcoin::Script.from_string("OP_ADD OP_DUP OP_DROP").raw }
@@ -240,20 +249,20 @@
it "11. For each input, if we are using the nth output of the earlier transaction, but it has fewer than n+1 outputs, reject this transaction" do
check_tx(@tx, [:prev_out, [[@tx.in[0].prev_out.reverse_hth, 2]]]) {|tx| tx.in[0].prev_out_index = 2 }
end
it "13. Verify crypto signatures for each input; reject if any are bad" do
- check_tx(@tx, [:signatures, [0]]) {|tx| @tx.in[0].script_sig[-1] = "\x00" }
+ check_tx(@tx, [:signatures, [0]]) {|tx| @tx.in[0].script_sig = "bad sig" }
end
it "14. For each input, if the referenced output has already been spent by a transaction in the main branch, reject this transaction" do
block2 = create_block(@block1.hash, true, [
->(tx) {create_tx(tx, @block1.tx.first, 0, [[50, @key]])}], @key)
if @store.class.name =~ /Utxo/
check_tx(@tx, [:prev_out, [[@tx.in[0].prev_out.reverse_hth, 0]]])
else
- check_tx(@tx, [:spent, [0]])
+ check_tx(@tx, [:not_spent, [0]])
end
end
it "15. Using the referenced output transactions to get input values, check that each input value, as well as the sum, are in legal money range" do
@store.db[@store.class.name =~ /Utxo/ ? :utxo : :txout].order(:id).reverse.limit(1).update(value: 22e14)
@@ -261,9 +270,30 @@
end
it "16. Reject if the sum of input values < sum of output values" do
tx = build_tx {|t| create_tx(t, @block1.tx.first, 0, [[100e8, @key]]) }
check_tx(tx, [:output_sum, [100e8, 50e8]])
+ end
+
+
+ it "should not allow double spend within the same block" do
+ # double-spend output from previous block
+ prev_tx = @block1.tx[0]
+ block = create_block @block1.hash, false, [
+ ->(t) { create_tx(t, prev_tx, 0, [[prev_tx.out[0].value, @key]]) },
+ ->(t) { create_tx(t, prev_tx, 0, [[prev_tx.out[0].value, @key]]) }
+ ]
+ -> { @store.store_block(block) }.should.raise(Bitcoin::Validation::ValidationError)
+
+ # double-spend output from current block
+ block = create_block @block1.hash, false, [
+ ->(t) { create_tx(t, prev_tx, 0, [[prev_tx.out[0].value, @key]]) }
+ ]
+ prev_tx = block.tx[1]
+ block.tx << build_tx {|t| create_tx(t, prev_tx, 0, [[prev_tx.out[0].value, @key]]) }
+ block.tx << build_tx {|t| create_tx(t, prev_tx, 0, [[prev_tx.out[0].value, @key]]) }
+ block.recalc_mrkl_root; block.recalc_block_hash
+ -> { @store.store_block(block) }.should.raise(Bitcoin::Validation::ValidationError)
end
end
end