302 lines
		
	
	
		
			8.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
		
		
			
		
	
	
			302 lines
		
	
	
		
			8.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| 
								 | 
							
								// Copyright 2021 The go-ethereum Authors
							 | 
						||
| 
								 | 
							
								// This file is part of the go-ethereum library.
							 | 
						||
| 
								 | 
							
								//
							 | 
						||
| 
								 | 
							
								// The go-ethereum library is free software: you can redistribute it and/or modify
							 | 
						||
| 
								 | 
							
								// it under the terms of the GNU Lesser General Public License as published by
							 | 
						||
| 
								 | 
							
								// the Free Software Foundation, either version 3 of the License, or
							 | 
						||
| 
								 | 
							
								// (at your option) any later version.
							 | 
						||
| 
								 | 
							
								//
							 | 
						||
| 
								 | 
							
								// The go-ethereum library is distributed in the hope that it will be useful,
							 | 
						||
| 
								 | 
							
								// but WITHOUT ANY WARRANTY; without even the implied warranty of
							 | 
						||
| 
								 | 
							
								// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
							 | 
						||
| 
								 | 
							
								// GNU Lesser General Public License for more details.
							 | 
						||
| 
								 | 
							
								//
							 | 
						||
| 
								 | 
							
								// You should have received a copy of the GNU Lesser General Public License
							 | 
						||
| 
								 | 
							
								// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								package rawdb
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								import (
							 | 
						||
| 
								 | 
							
									"bytes"
							 | 
						||
| 
								 | 
							
									"errors"
							 | 
						||
| 
								 | 
							
									"fmt"
							 | 
						||
| 
								 | 
							
									"io/ioutil"
							 | 
						||
| 
								 | 
							
									"math/big"
							 | 
						||
| 
								 | 
							
									"math/rand"
							 | 
						||
| 
								 | 
							
									"os"
							 | 
						||
| 
								 | 
							
									"sync"
							 | 
						||
| 
								 | 
							
									"testing"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									"github.com/ethereum/go-ethereum/ethdb"
							 | 
						||
| 
								 | 
							
									"github.com/ethereum/go-ethereum/rlp"
							 | 
						||
| 
								 | 
							
									"github.com/stretchr/testify/require"
							 | 
						||
| 
								 | 
							
								)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								var freezerTestTableDef = map[string]bool{"test": true}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								func TestFreezerModify(t *testing.T) {
							 | 
						||
| 
								 | 
							
									t.Parallel()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// Create test data.
							 | 
						||
| 
								 | 
							
									var valuesRaw [][]byte
							 | 
						||
| 
								 | 
							
									var valuesRLP []*big.Int
							 | 
						||
| 
								 | 
							
									for x := 0; x < 100; x++ {
							 | 
						||
| 
								 | 
							
										v := getChunk(256, x)
							 | 
						||
| 
								 | 
							
										valuesRaw = append(valuesRaw, v)
							 | 
						||
| 
								 | 
							
										iv := big.NewInt(int64(x))
							 | 
						||
| 
								 | 
							
										iv = iv.Exp(iv, iv, nil)
							 | 
						||
| 
								 | 
							
										valuesRLP = append(valuesRLP, iv)
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									tables := map[string]bool{"raw": true, "rlp": false}
							 | 
						||
| 
								 | 
							
									f, dir := newFreezerForTesting(t, tables)
							 | 
						||
| 
								 | 
							
									defer os.RemoveAll(dir)
							 | 
						||
| 
								 | 
							
									defer f.Close()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// Commit test data.
							 | 
						||
| 
								 | 
							
									_, err := f.ModifyAncients(func(op ethdb.AncientWriteOp) error {
							 | 
						||
| 
								 | 
							
										for i := range valuesRaw {
							 | 
						||
| 
								 | 
							
											if err := op.AppendRaw("raw", uint64(i), valuesRaw[i]); err != nil {
							 | 
						||
| 
								 | 
							
												return err
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											if err := op.Append("rlp", uint64(i), valuesRLP[i]); err != nil {
							 | 
						||
| 
								 | 
							
												return err
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										return nil
							 | 
						||
| 
								 | 
							
									})
							 | 
						||
| 
								 | 
							
									if err != nil {
							 | 
						||
| 
								 | 
							
										t.Fatal("ModifyAncients failed:", err)
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// Dump indexes.
							 | 
						||
| 
								 | 
							
									for _, table := range f.tables {
							 | 
						||
| 
								 | 
							
										t.Log(table.name, "index:", table.dumpIndexString(0, int64(len(valuesRaw))))
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// Read back test data.
							 | 
						||
| 
								 | 
							
									checkAncientCount(t, f, "raw", uint64(len(valuesRaw)))
							 | 
						||
| 
								 | 
							
									checkAncientCount(t, f, "rlp", uint64(len(valuesRLP)))
							 | 
						||
| 
								 | 
							
									for i := range valuesRaw {
							 | 
						||
| 
								 | 
							
										v, _ := f.Ancient("raw", uint64(i))
							 | 
						||
| 
								 | 
							
										if !bytes.Equal(v, valuesRaw[i]) {
							 | 
						||
| 
								 | 
							
											t.Fatalf("wrong raw value at %d: %x", i, v)
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										ivEnc, _ := f.Ancient("rlp", uint64(i))
							 | 
						||
| 
								 | 
							
										want, _ := rlp.EncodeToBytes(valuesRLP[i])
							 | 
						||
| 
								 | 
							
										if !bytes.Equal(ivEnc, want) {
							 | 
						||
| 
								 | 
							
											t.Fatalf("wrong RLP value at %d: %x", i, ivEnc)
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// This checks that ModifyAncients rolls back freezer updates
							 | 
						||
| 
								 | 
							
								// when the function passed to it returns an error.
							 | 
						||
| 
								 | 
							
								func TestFreezerModifyRollback(t *testing.T) {
							 | 
						||
| 
								 | 
							
									t.Parallel()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									f, dir := newFreezerForTesting(t, freezerTestTableDef)
							 | 
						||
| 
								 | 
							
									defer os.RemoveAll(dir)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									theError := errors.New("oops")
							 | 
						||
| 
								 | 
							
									_, err := f.ModifyAncients(func(op ethdb.AncientWriteOp) error {
							 | 
						||
| 
								 | 
							
										// Append three items. This creates two files immediately,
							 | 
						||
| 
								 | 
							
										// because the table size limit of the test freezer is 2048.
							 | 
						||
| 
								 | 
							
										require.NoError(t, op.AppendRaw("test", 0, make([]byte, 2048)))
							 | 
						||
| 
								 | 
							
										require.NoError(t, op.AppendRaw("test", 1, make([]byte, 2048)))
							 | 
						||
| 
								 | 
							
										require.NoError(t, op.AppendRaw("test", 2, make([]byte, 2048)))
							 | 
						||
| 
								 | 
							
										return theError
							 | 
						||
| 
								 | 
							
									})
							 | 
						||
| 
								 | 
							
									if err != theError {
							 | 
						||
| 
								 | 
							
										t.Errorf("ModifyAncients returned wrong error %q", err)
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									checkAncientCount(t, f, "test", 0)
							 | 
						||
| 
								 | 
							
									f.Close()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// Reopen and check that the rolled-back data doesn't reappear.
							 | 
						||
| 
								 | 
							
									tables := map[string]bool{"test": true}
							 | 
						||
| 
								 | 
							
									f2, err := newFreezer(dir, "", false, 2049, tables)
							 | 
						||
| 
								 | 
							
									if err != nil {
							 | 
						||
| 
								 | 
							
										t.Fatalf("can't reopen freezer after failed ModifyAncients: %v", err)
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									defer f2.Close()
							 | 
						||
| 
								 | 
							
									checkAncientCount(t, f2, "test", 0)
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// This test runs ModifyAncients and Ancient concurrently with each other.
							 | 
						||
| 
								 | 
							
								func TestFreezerConcurrentModifyRetrieve(t *testing.T) {
							 | 
						||
| 
								 | 
							
									t.Parallel()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									f, dir := newFreezerForTesting(t, freezerTestTableDef)
							 | 
						||
| 
								 | 
							
									defer os.RemoveAll(dir)
							 | 
						||
| 
								 | 
							
									defer f.Close()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									var (
							 | 
						||
| 
								 | 
							
										numReaders     = 5
							 | 
						||
| 
								 | 
							
										writeBatchSize = uint64(50)
							 | 
						||
| 
								 | 
							
										written        = make(chan uint64, numReaders*6)
							 | 
						||
| 
								 | 
							
										wg             sync.WaitGroup
							 | 
						||
| 
								 | 
							
									)
							 | 
						||
| 
								 | 
							
									wg.Add(numReaders + 1)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// Launch the writer. It appends 10000 items in batches.
							 | 
						||
| 
								 | 
							
									go func() {
							 | 
						||
| 
								 | 
							
										defer wg.Done()
							 | 
						||
| 
								 | 
							
										defer close(written)
							 | 
						||
| 
								 | 
							
										for item := uint64(0); item < 10000; item += writeBatchSize {
							 | 
						||
| 
								 | 
							
											_, err := f.ModifyAncients(func(op ethdb.AncientWriteOp) error {
							 | 
						||
| 
								 | 
							
												for i := uint64(0); i < writeBatchSize; i++ {
							 | 
						||
| 
								 | 
							
													item := item + i
							 | 
						||
| 
								 | 
							
													value := getChunk(32, int(item))
							 | 
						||
| 
								 | 
							
													if err := op.AppendRaw("test", item, value); err != nil {
							 | 
						||
| 
								 | 
							
														return err
							 | 
						||
| 
								 | 
							
													}
							 | 
						||
| 
								 | 
							
												}
							 | 
						||
| 
								 | 
							
												return nil
							 | 
						||
| 
								 | 
							
											})
							 | 
						||
| 
								 | 
							
											if err != nil {
							 | 
						||
| 
								 | 
							
												panic(err)
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											for i := 0; i < numReaders; i++ {
							 | 
						||
| 
								 | 
							
												written <- item + writeBatchSize
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
									}()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// Launch the readers. They read random items from the freezer up to the
							 | 
						||
| 
								 | 
							
									// current frozen item count.
							 | 
						||
| 
								 | 
							
									for i := 0; i < numReaders; i++ {
							 | 
						||
| 
								 | 
							
										go func() {
							 | 
						||
| 
								 | 
							
											defer wg.Done()
							 | 
						||
| 
								 | 
							
											for frozen := range written {
							 | 
						||
| 
								 | 
							
												for rc := 0; rc < 80; rc++ {
							 | 
						||
| 
								 | 
							
													num := uint64(rand.Intn(int(frozen)))
							 | 
						||
| 
								 | 
							
													value, err := f.Ancient("test", num)
							 | 
						||
| 
								 | 
							
													if err != nil {
							 | 
						||
| 
								 | 
							
														panic(fmt.Errorf("error reading %d (frozen %d): %v", num, frozen, err))
							 | 
						||
| 
								 | 
							
													}
							 | 
						||
| 
								 | 
							
													if !bytes.Equal(value, getChunk(32, int(num))) {
							 | 
						||
| 
								 | 
							
														panic(fmt.Errorf("wrong value at %d", num))
							 | 
						||
| 
								 | 
							
													}
							 | 
						||
| 
								 | 
							
												}
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
										}()
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									wg.Wait()
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// This test runs ModifyAncients and TruncateAncients concurrently with each other.
							 | 
						||
| 
								 | 
							
								func TestFreezerConcurrentModifyTruncate(t *testing.T) {
							 | 
						||
| 
								 | 
							
									f, dir := newFreezerForTesting(t, freezerTestTableDef)
							 | 
						||
| 
								 | 
							
									defer os.RemoveAll(dir)
							 | 
						||
| 
								 | 
							
									defer f.Close()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									var item = make([]byte, 256)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									for i := 0; i < 1000; i++ {
							 | 
						||
| 
								 | 
							
										// First reset and write 100 items.
							 | 
						||
| 
								 | 
							
										if err := f.TruncateAncients(0); err != nil {
							 | 
						||
| 
								 | 
							
											t.Fatal("truncate failed:", err)
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										_, err := f.ModifyAncients(func(op ethdb.AncientWriteOp) error {
							 | 
						||
| 
								 | 
							
											for i := uint64(0); i < 100; i++ {
							 | 
						||
| 
								 | 
							
												if err := op.AppendRaw("test", i, item); err != nil {
							 | 
						||
| 
								 | 
							
													return err
							 | 
						||
| 
								 | 
							
												}
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											return nil
							 | 
						||
| 
								 | 
							
										})
							 | 
						||
| 
								 | 
							
										if err != nil {
							 | 
						||
| 
								 | 
							
											t.Fatal("modify failed:", err)
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										checkAncientCount(t, f, "test", 100)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										// Now append 100 more items and truncate concurrently.
							 | 
						||
| 
								 | 
							
										var (
							 | 
						||
| 
								 | 
							
											wg          sync.WaitGroup
							 | 
						||
| 
								 | 
							
											truncateErr error
							 | 
						||
| 
								 | 
							
											modifyErr   error
							 | 
						||
| 
								 | 
							
										)
							 | 
						||
| 
								 | 
							
										wg.Add(3)
							 | 
						||
| 
								 | 
							
										go func() {
							 | 
						||
| 
								 | 
							
											_, modifyErr = f.ModifyAncients(func(op ethdb.AncientWriteOp) error {
							 | 
						||
| 
								 | 
							
												for i := uint64(100); i < 200; i++ {
							 | 
						||
| 
								 | 
							
													if err := op.AppendRaw("test", i, item); err != nil {
							 | 
						||
| 
								 | 
							
														return err
							 | 
						||
| 
								 | 
							
													}
							 | 
						||
| 
								 | 
							
												}
							 | 
						||
| 
								 | 
							
												return nil
							 | 
						||
| 
								 | 
							
											})
							 | 
						||
| 
								 | 
							
											wg.Done()
							 | 
						||
| 
								 | 
							
										}()
							 | 
						||
| 
								 | 
							
										go func() {
							 | 
						||
| 
								 | 
							
											truncateErr = f.TruncateAncients(10)
							 | 
						||
| 
								 | 
							
											wg.Done()
							 | 
						||
| 
								 | 
							
										}()
							 | 
						||
| 
								 | 
							
										go func() {
							 | 
						||
| 
								 | 
							
											f.AncientSize("test")
							 | 
						||
| 
								 | 
							
											wg.Done()
							 | 
						||
| 
								 | 
							
										}()
							 | 
						||
| 
								 | 
							
										wg.Wait()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										// Now check the outcome. If the truncate operation went through first, the append
							 | 
						||
| 
								 | 
							
										// fails, otherwise it succeeds. In either case, the freezer should be positioned
							 | 
						||
| 
								 | 
							
										// at 10 after both operations are done.
							 | 
						||
| 
								 | 
							
										if truncateErr != nil {
							 | 
						||
| 
								 | 
							
											t.Fatal("concurrent truncate failed:", err)
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										if !(modifyErr == nil || modifyErr == errOutOrderInsertion) {
							 | 
						||
| 
								 | 
							
											t.Fatal("wrong error from concurrent modify:", modifyErr)
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										checkAncientCount(t, f, "test", 10)
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								func newFreezerForTesting(t *testing.T, tables map[string]bool) (*freezer, string) {
							 | 
						||
| 
								 | 
							
									t.Helper()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									dir, err := ioutil.TempDir("", "freezer")
							 | 
						||
| 
								 | 
							
									if err != nil {
							 | 
						||
| 
								 | 
							
										t.Fatal(err)
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									// note: using low max table size here to ensure the tests actually
							 | 
						||
| 
								 | 
							
									// switch between multiple files.
							 | 
						||
| 
								 | 
							
									f, err := newFreezer(dir, "", false, 2049, tables)
							 | 
						||
| 
								 | 
							
									if err != nil {
							 | 
						||
| 
								 | 
							
										t.Fatal("can't open freezer", err)
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									return f, dir
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// checkAncientCount verifies that the freezer contains n items.
							 | 
						||
| 
								 | 
							
								func checkAncientCount(t *testing.T, f *freezer, kind string, n uint64) {
							 | 
						||
| 
								 | 
							
									t.Helper()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									if frozen, _ := f.Ancients(); frozen != n {
							 | 
						||
| 
								 | 
							
										t.Fatalf("Ancients() returned %d, want %d", frozen, n)
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// Check at index n-1.
							 | 
						||
| 
								 | 
							
									if n > 0 {
							 | 
						||
| 
								 | 
							
										index := n - 1
							 | 
						||
| 
								 | 
							
										if ok, _ := f.HasAncient(kind, index); !ok {
							 | 
						||
| 
								 | 
							
											t.Errorf("HasAncient(%q, %d) returned false unexpectedly", kind, index)
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										if _, err := f.Ancient(kind, index); err != nil {
							 | 
						||
| 
								 | 
							
											t.Errorf("Ancient(%q, %d) returned unexpected error %q", kind, index, err)
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// Check at index n.
							 | 
						||
| 
								 | 
							
									index := n
							 | 
						||
| 
								 | 
							
									if ok, _ := f.HasAncient(kind, index); ok {
							 | 
						||
| 
								 | 
							
										t.Errorf("HasAncient(%q, %d) returned true unexpectedly", kind, index)
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									if _, err := f.Ancient(kind, index); err == nil {
							 | 
						||
| 
								 | 
							
										t.Errorf("Ancient(%q, %d) didn't return expected error", kind, index)
							 | 
						||
| 
								 | 
							
									} else if err != errOutOfBounds {
							 | 
						||
| 
								 | 
							
										t.Errorf("Ancient(%q, %d) returned unexpected error %q", kind, index, err)
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								}
							 |