github.com/datachainlab/burrow@v0.25.0/execution/evm/memory.go (about)

     1  package evm
     2  
     3  import (
     4  	"fmt"
     5  	"math"
     6  	"math/big"
     7  
     8  	"github.com/hyperledger/burrow/execution/errors"
     9  )
    10  
    11  const (
    12  	defaultInitialMemoryCapacity = 0x100000  // 1 MiB
    13  	defaultMaximumMemoryCapacity = 0x1000000 // 16 MiB
    14  )
    15  
    16  // Change the length of this zero array to tweak the size of the block of zeros
    17  // written to the backing slice at a time when it is grown. A larger number may
    18  // lead to fewer calls to append to achieve the desired capacity although it is
    19  // unlikely to make a lot of difference.
    20  var zeroBlock []byte = make([]byte, 32)
    21  
    22  // Interface for a bounded linear memory indexed by a single *big.Int parameter
    23  // for each byte in the memory.
    24  type Memory interface {
    25  	// Read a value from the memory store starting at offset
    26  	// (index of first byte will equal offset). The value will be returned as a
    27  	// length-bytes byte slice. Returns an error if the memory cannot be read or
    28  	// is not allocated.
    29  	//
    30  	// The value returned should be copy of any underlying memory, not a reference
    31  	// to the underlying store.
    32  	Read(offset, length *big.Int) []byte
    33  	// Write a value to the memory starting at offset (the index of the first byte
    34  	// written will equal offset). The value is provided as bytes to be written
    35  	// consecutively to the memory store. Return an error if the memory cannot be
    36  	// written or allocated.
    37  	Write(offset *big.Int, value []byte)
    38  	// Returns the current capacity of the memory. For dynamically allocating
    39  	// memory this capacity can be used as a write offset that is guaranteed to be
    40  	// unused. Solidity in particular makes this assumption when using MSIZE to
    41  	// get the current allocated memory.
    42  	Capacity() *big.Int
    43  }
    44  
    45  // Get a new DynamicMemory (note that although we take a maximumCapacity of uint64 we currently
    46  // limit the maximum to int32 at runtime because we are using a single slice which we cannot guarantee
    47  // to be indexable above int32 or all validators
    48  func NewDynamicMemory(initialCapacity, maximumCapacity uint64, errSink errors.Sink) Memory {
    49  	return &dynamicMemory{
    50  		slice:           make([]byte, initialCapacity),
    51  		maximumCapacity: maximumCapacity,
    52  		errSink:         errSink,
    53  	}
    54  }
    55  
    56  func DefaultDynamicMemoryProvider(errSink errors.Sink) Memory {
    57  	return NewDynamicMemory(defaultInitialMemoryCapacity, defaultMaximumMemoryCapacity, errSink)
    58  }
    59  
    60  // Implements a bounded dynamic memory that relies on Go's (pretty good) dynamic
    61  // array allocation via a backing slice
    62  type dynamicMemory struct {
    63  	slice           []byte
    64  	maximumCapacity uint64
    65  	errSink         errors.Sink
    66  }
    67  
    68  func (mem *dynamicMemory) Read(offset, length *big.Int) []byte {
    69  	// Ensures positive and not too wide
    70  	if !offset.IsUint64() {
    71  		mem.pushErr(fmt.Errorf("offset %v does not fit inside an unsigned 64-bit integer", offset))
    72  		return nil
    73  	}
    74  	// Ensures positive and not too wide
    75  	if !length.IsUint64() {
    76  		mem.pushErr(fmt.Errorf("length %v does not fit inside an unsigned 64-bit integer", offset))
    77  		return nil
    78  	}
    79  	output, err := mem.read(offset.Uint64(), length.Uint64())
    80  	if err != nil {
    81  		mem.pushErr(err)
    82  		return nil
    83  	}
    84  	return output
    85  }
    86  
    87  func (mem *dynamicMemory) Write(offset *big.Int, value []byte) {
    88  	// Ensures positive and not too wide
    89  	if !offset.IsUint64() {
    90  		mem.pushErr(fmt.Errorf("offset %v does not fit inside an unsigned 64-bit integer", offset))
    91  		return
    92  	}
    93  	err := mem.write(offset.Uint64(), value)
    94  	if err != nil {
    95  		mem.pushErr(err)
    96  	}
    97  }
    98  
    99  func (mem *dynamicMemory) Capacity() *big.Int {
   100  	return big.NewInt(int64(len(mem.slice)))
   101  }
   102  
   103  func (mem *dynamicMemory) read(offset, length uint64) ([]byte, error) {
   104  	capacity := offset + length
   105  	err := mem.ensureCapacity(capacity)
   106  	if err != nil {
   107  		return nil, err
   108  	}
   109  	value := make([]byte, length)
   110  	copy(value, mem.slice[offset:capacity])
   111  	return value, nil
   112  }
   113  
   114  func (mem *dynamicMemory) write(offset uint64, value []byte) error {
   115  	capacity := offset + uint64(len(value))
   116  	err := mem.ensureCapacity(capacity)
   117  	if err != nil {
   118  		return err
   119  	}
   120  	copy(mem.slice[offset:capacity], value)
   121  	return nil
   122  }
   123  
   124  func (mem *dynamicMemory) pushErr(err error) {
   125  	mem.errSink.PushError(err)
   126  }
   127  
   128  // Ensures the current memory store can hold newCapacity. Will only grow the
   129  // memory (will not shrink).
   130  func (mem *dynamicMemory) ensureCapacity(newCapacity uint64) error {
   131  	// Maximum length of a slice that allocates memory is the same as the native int max size
   132  	// We could rethink this limit, but we don't want different validators to disagree on
   133  	// transaction validity so we pick the lowest common denominator
   134  	if newCapacity > math.MaxInt32 {
   135  		// If we ever did want more than an int32 of space then we would need to
   136  		// maintain multiple pages of memory
   137  		return fmt.Errorf("cannot address memory beyond a maximum index "+
   138  			"with int32 width (%v bytes)", math.MaxInt32)
   139  	}
   140  	newCapacityInt := int(newCapacity)
   141  	// We're already big enough so return
   142  	if newCapacityInt <= len(mem.slice) {
   143  		return nil
   144  	}
   145  	if newCapacity > mem.maximumCapacity {
   146  		return fmt.Errorf("cannot grow memory because it would exceed the "+
   147  			"current maximum limit of %v bytes", mem.maximumCapacity)
   148  	}
   149  	// Ensure the backing array of slice is big enough
   150  	// Grow the memory one word at time using the pre-allocated zeroBlock to avoid
   151  	// unnecessary allocations. Use append to make use of any spare capacity in
   152  	// the slice's backing array.
   153  	for newCapacityInt > cap(mem.slice) {
   154  		// We'll trust Go exponentially grow our arrays (at first).
   155  		mem.slice = append(mem.slice, zeroBlock...)
   156  	}
   157  	// Now we've ensured the backing array of the slice is big enough we can
   158  	// just re-slice (even if len(mem.slice) < newCapacity)
   159  	mem.slice = mem.slice[:newCapacity]
   160  	return nil
   161  }