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