mirror of https://github.com/ethereum/go-ethereum
commit
e5532154a5
@ -0,0 +1,26 @@ |
||||
/* |
||||
Package cl provides a binding to the OpenCL api. It's mostly a low-level |
||||
wrapper that avoids adding functionality while still making the interface |
||||
a little more friendly and easy to use. |
||||
|
||||
Resource life-cycle management: |
||||
|
||||
For any CL object that gets created (buffer, queue, kernel, etc..) you should |
||||
call object.Release() when finished with it to free the CL resources. This |
||||
explicitely calls the related clXXXRelease method for the type. However, |
||||
as a fallback there is a finalizer set for every resource item that takes |
||||
care of it (eventually) if Release isn't called. In this way you can have |
||||
better control over the life cycle of resources while having a fall back |
||||
to avoid leaks. This is similar to how file handles and such are handled |
||||
in the Go standard packages. |
||||
*/ |
||||
package cl |
||||
|
||||
// #include "headers/1.2/opencl.h"
|
||||
// #cgo CFLAGS: -Iheaders/1.2
|
||||
// #cgo darwin LDFLAGS: -framework OpenCL
|
||||
// #cgo linux LDFLAGS: -lOpenCL
|
||||
import "C" |
||||
import "errors" |
||||
|
||||
var ErrUnsupported = errors.New("cl: unsupported") |
@ -0,0 +1,254 @@ |
||||
package cl |
||||
|
||||
import ( |
||||
"math/rand" |
||||
"reflect" |
||||
"strings" |
||||
"testing" |
||||
) |
||||
|
||||
var kernelSource = ` |
||||
__kernel void square( |
||||
__global float* input, |
||||
__global float* output, |
||||
const unsigned int count) |
||||
{ |
||||
int i = get_global_id(0); |
||||
if(i < count) |
||||
output[i] = input[i] * input[i]; |
||||
} |
||||
` |
||||
|
||||
func getObjectStrings(object interface{}) map[string]string { |
||||
v := reflect.ValueOf(object) |
||||
t := reflect.TypeOf(object) |
||||
|
||||
strs := make(map[string]string) |
||||
|
||||
numMethods := t.NumMethod() |
||||
for i := 0; i < numMethods; i++ { |
||||
method := t.Method(i) |
||||
if method.Type.NumIn() == 1 && method.Type.NumOut() == 1 && method.Type.Out(0).Kind() == reflect.String { |
||||
// this is a string-returning method with (presumably) only a pointer receiver parameter
|
||||
// call it
|
||||
outs := v.Method(i).Call([]reflect.Value{}) |
||||
// put the result in our map
|
||||
strs[method.Name] = (outs[0].Interface()).(string) |
||||
} |
||||
} |
||||
|
||||
return strs |
||||
} |
||||
|
||||
func TestPlatformStringsContainNoNULs(t *testing.T) { |
||||
platforms, err := GetPlatforms() |
||||
if err != nil { |
||||
t.Fatalf("Failed to get platforms: %+v", err) |
||||
} |
||||
|
||||
for _, p := range platforms { |
||||
for key, value := range getObjectStrings(p) { |
||||
if strings.Contains(value, "\x00") { |
||||
t.Fatalf("platform string %q = %+q contains NUL", key, value) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestDeviceStringsContainNoNULs(t *testing.T) { |
||||
platforms, err := GetPlatforms() |
||||
if err != nil { |
||||
t.Fatalf("Failed to get platforms: %+v", err) |
||||
} |
||||
|
||||
for _, p := range platforms { |
||||
devs, err := p.GetDevices(DeviceTypeAll) |
||||
if err != nil { |
||||
t.Fatalf("Failed to get devices for platform %q: %+v", p.Name(), err) |
||||
} |
||||
|
||||
for _, d := range devs { |
||||
for key, value := range getObjectStrings(d) { |
||||
if strings.Contains(value, "\x00") { |
||||
t.Fatalf("device string %q = %+q contains NUL", key, value) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestHello(t *testing.T) { |
||||
var data [1024]float32 |
||||
for i := 0; i < len(data); i++ { |
||||
data[i] = rand.Float32() |
||||
} |
||||
|
||||
platforms, err := GetPlatforms() |
||||
if err != nil { |
||||
t.Fatalf("Failed to get platforms: %+v", err) |
||||
} |
||||
for i, p := range platforms { |
||||
t.Logf("Platform %d:", i) |
||||
t.Logf(" Name: %s", p.Name()) |
||||
t.Logf(" Vendor: %s", p.Vendor()) |
||||
t.Logf(" Profile: %s", p.Profile()) |
||||
t.Logf(" Version: %s", p.Version()) |
||||
t.Logf(" Extensions: %s", p.Extensions()) |
||||
} |
||||
platform := platforms[0] |
||||
|
||||
devices, err := platform.GetDevices(DeviceTypeAll) |
||||
if err != nil { |
||||
t.Fatalf("Failed to get devices: %+v", err) |
||||
} |
||||
if len(devices) == 0 { |
||||
t.Fatalf("GetDevices returned no devices") |
||||
} |
||||
deviceIndex := -1 |
||||
for i, d := range devices { |
||||
if deviceIndex < 0 && d.Type() == DeviceTypeGPU { |
||||
deviceIndex = i |
||||
} |
||||
t.Logf("Device %d (%s): %s", i, d.Type(), d.Name()) |
||||
t.Logf(" Address Bits: %d", d.AddressBits()) |
||||
t.Logf(" Available: %+v", d.Available()) |
||||
// t.Logf(" Built-In Kernels: %s", d.BuiltInKernels())
|
||||
t.Logf(" Compiler Available: %+v", d.CompilerAvailable()) |
||||
t.Logf(" Double FP Config: %s", d.DoubleFPConfig()) |
||||
t.Logf(" Driver Version: %s", d.DriverVersion()) |
||||
t.Logf(" Error Correction Supported: %+v", d.ErrorCorrectionSupport()) |
||||
t.Logf(" Execution Capabilities: %s", d.ExecutionCapabilities()) |
||||
t.Logf(" Extensions: %s", d.Extensions()) |
||||
t.Logf(" Global Memory Cache Type: %s", d.GlobalMemCacheType()) |
||||
t.Logf(" Global Memory Cacheline Size: %d KB", d.GlobalMemCachelineSize()/1024) |
||||
t.Logf(" Global Memory Size: %d MB", d.GlobalMemSize()/(1024*1024)) |
||||
t.Logf(" Half FP Config: %s", d.HalfFPConfig()) |
||||
t.Logf(" Host Unified Memory: %+v", d.HostUnifiedMemory()) |
||||
t.Logf(" Image Support: %+v", d.ImageSupport()) |
||||
t.Logf(" Image2D Max Dimensions: %d x %d", d.Image2DMaxWidth(), d.Image2DMaxHeight()) |
||||
t.Logf(" Image3D Max Dimenionns: %d x %d x %d", d.Image3DMaxWidth(), d.Image3DMaxHeight(), d.Image3DMaxDepth()) |
||||
// t.Logf(" Image Max Buffer Size: %d", d.ImageMaxBufferSize())
|
||||
// t.Logf(" Image Max Array Size: %d", d.ImageMaxArraySize())
|
||||
// t.Logf(" Linker Available: %+v", d.LinkerAvailable())
|
||||
t.Logf(" Little Endian: %+v", d.EndianLittle()) |
||||
t.Logf(" Local Mem Size Size: %d KB", d.LocalMemSize()/1024) |
||||
t.Logf(" Local Mem Type: %s", d.LocalMemType()) |
||||
t.Logf(" Max Clock Frequency: %d", d.MaxClockFrequency()) |
||||
t.Logf(" Max Compute Units: %d", d.MaxComputeUnits()) |
||||
t.Logf(" Max Constant Args: %d", d.MaxConstantArgs()) |
||||
t.Logf(" Max Constant Buffer Size: %d KB", d.MaxConstantBufferSize()/1024) |
||||
t.Logf(" Max Mem Alloc Size: %d KB", d.MaxMemAllocSize()/1024) |
||||
t.Logf(" Max Parameter Size: %d", d.MaxParameterSize()) |
||||
t.Logf(" Max Read-Image Args: %d", d.MaxReadImageArgs()) |
||||
t.Logf(" Max Samplers: %d", d.MaxSamplers()) |
||||
t.Logf(" Max Work Group Size: %d", d.MaxWorkGroupSize()) |
||||
t.Logf(" Max Work Item Dimensions: %d", d.MaxWorkItemDimensions()) |
||||
t.Logf(" Max Work Item Sizes: %d", d.MaxWorkItemSizes()) |
||||
t.Logf(" Max Write-Image Args: %d", d.MaxWriteImageArgs()) |
||||
t.Logf(" Memory Base Address Alignment: %d", d.MemBaseAddrAlign()) |
||||
t.Logf(" Native Vector Width Char: %d", d.NativeVectorWidthChar()) |
||||
t.Logf(" Native Vector Width Short: %d", d.NativeVectorWidthShort()) |
||||
t.Logf(" Native Vector Width Int: %d", d.NativeVectorWidthInt()) |
||||
t.Logf(" Native Vector Width Long: %d", d.NativeVectorWidthLong()) |
||||
t.Logf(" Native Vector Width Float: %d", d.NativeVectorWidthFloat()) |
||||
t.Logf(" Native Vector Width Double: %d", d.NativeVectorWidthDouble()) |
||||
t.Logf(" Native Vector Width Half: %d", d.NativeVectorWidthHalf()) |
||||
t.Logf(" OpenCL C Version: %s", d.OpenCLCVersion()) |
||||
// t.Logf(" Parent Device: %+v", d.ParentDevice())
|
||||
t.Logf(" Profile: %s", d.Profile()) |
||||
t.Logf(" Profiling Timer Resolution: %d", d.ProfilingTimerResolution()) |
||||
t.Logf(" Vendor: %s", d.Vendor()) |
||||
t.Logf(" Version: %s", d.Version()) |
||||
} |
||||
if deviceIndex < 0 { |
||||
deviceIndex = 0 |
||||
} |
||||
device := devices[deviceIndex] |
||||
t.Logf("Using device %d", deviceIndex) |
||||
context, err := CreateContext([]*Device{device}) |
||||
if err != nil { |
||||
t.Fatalf("CreateContext failed: %+v", err) |
||||
} |
||||
// imageFormats, err := context.GetSupportedImageFormats(0, MemObjectTypeImage2D)
|
||||
// if err != nil {
|
||||
// t.Fatalf("GetSupportedImageFormats failed: %+v", err)
|
||||
// }
|
||||
// t.Logf("Supported image formats: %+v", imageFormats)
|
||||
queue, err := context.CreateCommandQueue(device, 0) |
||||
if err != nil { |
||||
t.Fatalf("CreateCommandQueue failed: %+v", err) |
||||
} |
||||
program, err := context.CreateProgramWithSource([]string{kernelSource}) |
||||
if err != nil { |
||||
t.Fatalf("CreateProgramWithSource failed: %+v", err) |
||||
} |
||||
if err := program.BuildProgram(nil, ""); err != nil { |
||||
t.Fatalf("BuildProgram failed: %+v", err) |
||||
} |
||||
kernel, err := program.CreateKernel("square") |
||||
if err != nil { |
||||
t.Fatalf("CreateKernel failed: %+v", err) |
||||
} |
||||
for i := 0; i < 3; i++ { |
||||
name, err := kernel.ArgName(i) |
||||
if err == ErrUnsupported { |
||||
break |
||||
} else if err != nil { |
||||
t.Errorf("GetKernelArgInfo for name failed: %+v", err) |
||||
break |
||||
} else { |
||||
t.Logf("Kernel arg %d: %s", i, name) |
||||
} |
||||
} |
||||
input, err := context.CreateEmptyBuffer(MemReadOnly, 4*len(data)) |
||||
if err != nil { |
||||
t.Fatalf("CreateBuffer failed for input: %+v", err) |
||||
} |
||||
output, err := context.CreateEmptyBuffer(MemReadOnly, 4*len(data)) |
||||
if err != nil { |
||||
t.Fatalf("CreateBuffer failed for output: %+v", err) |
||||
} |
||||
if _, err := queue.EnqueueWriteBufferFloat32(input, true, 0, data[:], nil); err != nil { |
||||
t.Fatalf("EnqueueWriteBufferFloat32 failed: %+v", err) |
||||
} |
||||
if err := kernel.SetArgs(input, output, uint32(len(data))); err != nil { |
||||
t.Fatalf("SetKernelArgs failed: %+v", err) |
||||
} |
||||
|
||||
local, err := kernel.WorkGroupSize(device) |
||||
if err != nil { |
||||
t.Fatalf("WorkGroupSize failed: %+v", err) |
||||
} |
||||
t.Logf("Work group size: %d", local) |
||||
size, _ := kernel.PreferredWorkGroupSizeMultiple(nil) |
||||
t.Logf("Preferred Work Group Size Multiple: %d", size) |
||||
|
||||
global := len(data) |
||||
d := len(data) % local |
||||
if d != 0 { |
||||
global += local - d |
||||
} |
||||
if _, err := queue.EnqueueNDRangeKernel(kernel, nil, []int{global}, []int{local}, nil); err != nil { |
||||
t.Fatalf("EnqueueNDRangeKernel failed: %+v", err) |
||||
} |
||||
|
||||
if err := queue.Finish(); err != nil { |
||||
t.Fatalf("Finish failed: %+v", err) |
||||
} |
||||
|
||||
results := make([]float32, len(data)) |
||||
if _, err := queue.EnqueueReadBufferFloat32(output, true, 0, results, nil); err != nil { |
||||
t.Fatalf("EnqueueReadBufferFloat32 failed: %+v", err) |
||||
} |
||||
|
||||
correct := 0 |
||||
for i, v := range data { |
||||
if results[i] == v*v { |
||||
correct++ |
||||
} |
||||
} |
||||
|
||||
if correct != len(data) { |
||||
t.Fatalf("%d/%d correct values", correct, len(data)) |
||||
} |
||||
} |
@ -0,0 +1,161 @@ |
||||
package cl |
||||
|
||||
// #include <stdlib.h>
|
||||
// #ifdef __APPLE__
|
||||
// #include "OpenCL/opencl.h"
|
||||
// #else
|
||||
// #include "cl.h"
|
||||
// #endif
|
||||
import "C" |
||||
|
||||
import ( |
||||
"runtime" |
||||
"unsafe" |
||||
) |
||||
|
||||
const maxImageFormats = 256 |
||||
|
||||
type Context struct { |
||||
clContext C.cl_context |
||||
devices []*Device |
||||
} |
||||
|
||||
type MemObject struct { |
||||
clMem C.cl_mem |
||||
size int |
||||
} |
||||
|
||||
func releaseContext(c *Context) { |
||||
if c.clContext != nil { |
||||
C.clReleaseContext(c.clContext) |
||||
c.clContext = nil |
||||
} |
||||
} |
||||
|
||||
func releaseMemObject(b *MemObject) { |
||||
if b.clMem != nil { |
||||
C.clReleaseMemObject(b.clMem) |
||||
b.clMem = nil |
||||
} |
||||
} |
||||
|
||||
func newMemObject(mo C.cl_mem, size int) *MemObject { |
||||
memObject := &MemObject{clMem: mo, size: size} |
||||
runtime.SetFinalizer(memObject, releaseMemObject) |
||||
return memObject |
||||
} |
||||
|
||||
func (b *MemObject) Release() { |
||||
releaseMemObject(b) |
||||
} |
||||
|
||||
// TODO: properties
|
||||
func CreateContext(devices []*Device) (*Context, error) { |
||||
deviceIds := buildDeviceIdList(devices) |
||||
var err C.cl_int |
||||
clContext := C.clCreateContext(nil, C.cl_uint(len(devices)), &deviceIds[0], nil, nil, &err) |
||||
if err != C.CL_SUCCESS { |
||||
return nil, toError(err) |
||||
} |
||||
if clContext == nil { |
||||
return nil, ErrUnknown |
||||
} |
||||
context := &Context{clContext: clContext, devices: devices} |
||||
runtime.SetFinalizer(context, releaseContext) |
||||
return context, nil |
||||
} |
||||
|
||||
func (ctx *Context) GetSupportedImageFormats(flags MemFlag, imageType MemObjectType) ([]ImageFormat, error) { |
||||
var formats [maxImageFormats]C.cl_image_format |
||||
var nFormats C.cl_uint |
||||
if err := C.clGetSupportedImageFormats(ctx.clContext, C.cl_mem_flags(flags), C.cl_mem_object_type(imageType), maxImageFormats, &formats[0], &nFormats); err != C.CL_SUCCESS { |
||||
return nil, toError(err) |
||||
} |
||||
fmts := make([]ImageFormat, nFormats) |
||||
for i, f := range formats[:nFormats] { |
||||
fmts[i] = ImageFormat{ |
||||
ChannelOrder: ChannelOrder(f.image_channel_order), |
||||
ChannelDataType: ChannelDataType(f.image_channel_data_type), |
||||
} |
||||
} |
||||
return fmts, nil |
||||
} |
||||
|
||||
func (ctx *Context) CreateCommandQueue(device *Device, properties CommandQueueProperty) (*CommandQueue, error) { |
||||
var err C.cl_int |
||||
clQueue := C.clCreateCommandQueue(ctx.clContext, device.id, C.cl_command_queue_properties(properties), &err) |
||||
if err != C.CL_SUCCESS { |
||||
return nil, toError(err) |
||||
} |
||||
if clQueue == nil { |
||||
return nil, ErrUnknown |
||||
} |
||||
commandQueue := &CommandQueue{clQueue: clQueue, device: device} |
||||
runtime.SetFinalizer(commandQueue, releaseCommandQueue) |
||||
return commandQueue, nil |
||||
} |
||||
|
||||
func (ctx *Context) CreateProgramWithSource(sources []string) (*Program, error) { |
||||
cSources := make([]*C.char, len(sources)) |
||||
for i, s := range sources { |
||||
cs := C.CString(s) |
||||
cSources[i] = cs |
||||
defer C.free(unsafe.Pointer(cs)) |
||||
} |
||||
var err C.cl_int |
||||
clProgram := C.clCreateProgramWithSource(ctx.clContext, C.cl_uint(len(sources)), &cSources[0], nil, &err) |
||||
if err != C.CL_SUCCESS { |
||||
return nil, toError(err) |
||||
} |
||||
if clProgram == nil { |
||||
return nil, ErrUnknown |
||||
} |
||||
program := &Program{clProgram: clProgram, devices: ctx.devices} |
||||
runtime.SetFinalizer(program, releaseProgram) |
||||
return program, nil |
||||
} |
||||
|
||||
func (ctx *Context) CreateBufferUnsafe(flags MemFlag, size int, dataPtr unsafe.Pointer) (*MemObject, error) { |
||||
var err C.cl_int |
||||
clBuffer := C.clCreateBuffer(ctx.clContext, C.cl_mem_flags(flags), C.size_t(size), dataPtr, &err) |
||||
if err != C.CL_SUCCESS { |
||||
return nil, toError(err) |
||||
} |
||||
if clBuffer == nil { |
||||
return nil, ErrUnknown |
||||
} |
||||
return newMemObject(clBuffer, size), nil |
||||
} |
||||
|
||||
func (ctx *Context) CreateEmptyBuffer(flags MemFlag, size int) (*MemObject, error) { |
||||
return ctx.CreateBufferUnsafe(flags, size, nil) |
||||
} |
||||
|
||||
func (ctx *Context) CreateEmptyBufferFloat32(flags MemFlag, size int) (*MemObject, error) { |
||||
return ctx.CreateBufferUnsafe(flags, 4*size, nil) |
||||
} |
||||
|
||||
func (ctx *Context) CreateBuffer(flags MemFlag, data []byte) (*MemObject, error) { |
||||
return ctx.CreateBufferUnsafe(flags, len(data), unsafe.Pointer(&data[0])) |
||||
} |
||||
|
||||
//float64
|
||||
func (ctx *Context) CreateBufferFloat32(flags MemFlag, data []float32) (*MemObject, error) { |
||||
return ctx.CreateBufferUnsafe(flags, 4*len(data), unsafe.Pointer(&data[0])) |
||||
} |
||||
|
||||
func (ctx *Context) CreateUserEvent() (*Event, error) { |
||||
var err C.cl_int |
||||
clEvent := C.clCreateUserEvent(ctx.clContext, &err) |
||||
if err != C.CL_SUCCESS { |
||||
return nil, toError(err) |
||||
} |
||||
return newEvent(clEvent), nil |
||||
} |
||||
|
||||
func (ctx *Context) Release() { |
||||
releaseContext(ctx) |
||||
} |
||||
|
||||
// http://www.khronos.org/registry/cl/sdk/1.2/docs/man/xhtml/clCreateSubBuffer.html
|
||||
// func (memObject *MemObject) CreateSubBuffer(flags MemFlag, bufferCreateType BufferCreateType, )
|
@ -0,0 +1,510 @@ |
||||
package cl |
||||
|
||||
// #ifdef __APPLE__
|
||||
// #include "OpenCL/opencl.h"
|
||||
// #else
|
||||
// #include "cl.h"
|
||||
// #include "cl_ext.h"
|
||||
// #endif
|
||||
import "C" |
||||
|
||||
import ( |
||||
"strings" |
||||
"unsafe" |
||||
) |
||||
|
||||
const maxDeviceCount = 64 |
||||
|
||||
type DeviceType uint |
||||
|
||||
const ( |
||||
DeviceTypeCPU DeviceType = C.CL_DEVICE_TYPE_CPU |
||||
DeviceTypeGPU DeviceType = C.CL_DEVICE_TYPE_GPU |
||||
DeviceTypeAccelerator DeviceType = C.CL_DEVICE_TYPE_ACCELERATOR |
||||
DeviceTypeDefault DeviceType = C.CL_DEVICE_TYPE_DEFAULT |
||||
DeviceTypeAll DeviceType = C.CL_DEVICE_TYPE_ALL |
||||
) |
||||
|
||||
type FPConfig int |
||||
|
||||
const ( |
||||
FPConfigDenorm FPConfig = C.CL_FP_DENORM // denorms are supported
|
||||
FPConfigInfNaN FPConfig = C.CL_FP_INF_NAN // INF and NaNs are supported
|
||||
FPConfigRoundToNearest FPConfig = C.CL_FP_ROUND_TO_NEAREST // round to nearest even rounding mode supported
|
||||
FPConfigRoundToZero FPConfig = C.CL_FP_ROUND_TO_ZERO // round to zero rounding mode supported
|
||||
FPConfigRoundToInf FPConfig = C.CL_FP_ROUND_TO_INF // round to positive and negative infinity rounding modes supported
|
||||
FPConfigFMA FPConfig = C.CL_FP_FMA // IEEE754-2008 fused multiply-add is supported
|
||||
FPConfigSoftFloat FPConfig = C.CL_FP_SOFT_FLOAT // Basic floating-point operations (such as addition, subtraction, multiplication) are implemented in software
|
||||
) |
||||
|
||||
var fpConfigNameMap = map[FPConfig]string{ |
||||
FPConfigDenorm: "Denorm", |
||||
FPConfigInfNaN: "InfNaN", |
||||
FPConfigRoundToNearest: "RoundToNearest", |
||||
FPConfigRoundToZero: "RoundToZero", |
||||
FPConfigRoundToInf: "RoundToInf", |
||||
FPConfigFMA: "FMA", |
||||
FPConfigSoftFloat: "SoftFloat", |
||||
} |
||||
|
||||
func (c FPConfig) String() string { |
||||
var parts []string |
||||
for bit, name := range fpConfigNameMap { |
||||
if c&bit != 0 { |
||||
parts = append(parts, name) |
||||
} |
||||
} |
||||
if parts == nil { |
||||
return "" |
||||
} |
||||
return strings.Join(parts, "|") |
||||
} |
||||
|
||||
func (dt DeviceType) String() string { |
||||
var parts []string |
||||
if dt&DeviceTypeCPU != 0 { |
||||
parts = append(parts, "CPU") |
||||
} |
||||
if dt&DeviceTypeGPU != 0 { |
||||
parts = append(parts, "GPU") |
||||
} |
||||
if dt&DeviceTypeAccelerator != 0 { |
||||
parts = append(parts, "Accelerator") |
||||
} |
||||
if dt&DeviceTypeDefault != 0 { |
||||
parts = append(parts, "Default") |
||||
} |
||||
if parts == nil { |
||||
parts = append(parts, "None") |
||||
} |
||||
return strings.Join(parts, "|") |
||||
} |
||||
|
||||
type Device struct { |
||||
id C.cl_device_id |
||||
} |
||||
|
||||
func buildDeviceIdList(devices []*Device) []C.cl_device_id { |
||||
deviceIds := make([]C.cl_device_id, len(devices)) |
||||
for i, d := range devices { |
||||
deviceIds[i] = d.id |
||||
} |
||||
return deviceIds |
||||
} |
||||
|
||||
// Obtain the list of devices available on a platform. 'platform' refers
|
||||
// to the platform returned by GetPlatforms or can be nil. If platform
|
||||
// is nil, the behavior is implementation-defined.
|
||||
func GetDevices(platform *Platform, deviceType DeviceType) ([]*Device, error) { |
||||
var deviceIds [maxDeviceCount]C.cl_device_id |
||||
var numDevices C.cl_uint |
||||
var platformId C.cl_platform_id |
||||
if platform != nil { |
||||
platformId = platform.id |
||||
} |
||||
if err := C.clGetDeviceIDs(platformId, C.cl_device_type(deviceType), C.cl_uint(maxDeviceCount), &deviceIds[0], &numDevices); err != C.CL_SUCCESS { |
||||
return nil, toError(err) |
||||
} |
||||
if numDevices > maxDeviceCount { |
||||
numDevices = maxDeviceCount |
||||
} |
||||
devices := make([]*Device, numDevices) |
||||
for i := 0; i < int(numDevices); i++ { |
||||
devices[i] = &Device{id: deviceIds[i]} |
||||
} |
||||
return devices, nil |
||||
} |
||||
|
||||
func (d *Device) nullableId() C.cl_device_id { |
||||
if d == nil { |
||||
return nil |
||||
} |
||||
return d.id |
||||
} |
||||
|
||||
func (d *Device) GetInfoString(param C.cl_device_info, panicOnError bool) (string, error) { |
||||
var strC [1024]C.char |
||||
var strN C.size_t |
||||
if err := C.clGetDeviceInfo(d.id, param, 1024, unsafe.Pointer(&strC), &strN); err != C.CL_SUCCESS { |
||||
if panicOnError { |
||||
panic("Should never fail") |
||||
} |
||||
return "", toError(err) |
||||
} |
||||
|
||||
// OpenCL strings are NUL-terminated, and the terminator is included in strN
|
||||
// Go strings aren't NUL-terminated, so subtract 1 from the length
|
||||
return C.GoStringN((*C.char)(unsafe.Pointer(&strC)), C.int(strN-1)), nil |
||||
} |
||||
|
||||
func (d *Device) getInfoUint(param C.cl_device_info, panicOnError bool) (uint, error) { |
||||
var val C.cl_uint |
||||
if err := C.clGetDeviceInfo(d.id, param, C.size_t(unsafe.Sizeof(val)), unsafe.Pointer(&val), nil); err != C.CL_SUCCESS { |
||||
if panicOnError { |
||||
panic("Should never fail") |
||||
} |
||||
return 0, toError(err) |
||||
} |
||||
return uint(val), nil |
||||
} |
||||
|
||||
func (d *Device) getInfoSize(param C.cl_device_info, panicOnError bool) (int, error) { |
||||
var val C.size_t |
||||
if err := C.clGetDeviceInfo(d.id, param, C.size_t(unsafe.Sizeof(val)), unsafe.Pointer(&val), nil); err != C.CL_SUCCESS { |
||||
if panicOnError { |
||||
panic("Should never fail") |
||||
} |
||||
return 0, toError(err) |
||||
} |
||||
return int(val), nil |
||||
} |
||||
|
||||
func (d *Device) getInfoUlong(param C.cl_device_info, panicOnError bool) (int64, error) { |
||||
var val C.cl_ulong |
||||
if err := C.clGetDeviceInfo(d.id, param, C.size_t(unsafe.Sizeof(val)), unsafe.Pointer(&val), nil); err != C.CL_SUCCESS { |
||||
if panicOnError { |
||||
panic("Should never fail") |
||||
} |
||||
return 0, toError(err) |
||||
} |
||||
return int64(val), nil |
||||
} |
||||
|
||||
func (d *Device) getInfoBool(param C.cl_device_info, panicOnError bool) (bool, error) { |
||||
var val C.cl_bool |
||||
if err := C.clGetDeviceInfo(d.id, param, C.size_t(unsafe.Sizeof(val)), unsafe.Pointer(&val), nil); err != C.CL_SUCCESS { |
||||
if panicOnError { |
||||
panic("Should never fail") |
||||
} |
||||
return false, toError(err) |
||||
} |
||||
return val == C.CL_TRUE, nil |
||||
} |
||||
|
||||
func (d *Device) Name() string { |
||||
str, _ := d.GetInfoString(C.CL_DEVICE_NAME, true) |
||||
return str |
||||
} |
||||
|
||||
func (d *Device) Vendor() string { |
||||
str, _ := d.GetInfoString(C.CL_DEVICE_VENDOR, true) |
||||
return str |
||||
} |
||||
|
||||
func (d *Device) Extensions() string { |
||||
str, _ := d.GetInfoString(C.CL_DEVICE_EXTENSIONS, true) |
||||
return str |
||||
} |
||||
|
||||
func (d *Device) OpenCLCVersion() string { |
||||
str, _ := d.GetInfoString(C.CL_DEVICE_OPENCL_C_VERSION, true) |
||||
return str |
||||
} |
||||
|
||||
func (d *Device) Profile() string { |
||||
str, _ := d.GetInfoString(C.CL_DEVICE_PROFILE, true) |
||||
return str |
||||
} |
||||
|
||||
func (d *Device) Version() string { |
||||
str, _ := d.GetInfoString(C.CL_DEVICE_VERSION, true) |
||||
return str |
||||
} |
||||
|
||||
func (d *Device) DriverVersion() string { |
||||
str, _ := d.GetInfoString(C.CL_DRIVER_VERSION, true) |
||||
return str |
||||
} |
||||
|
||||
// The default compute device address space size specified as an
|
||||
// unsigned integer value in bits. Currently supported values are 32 or 64 bits.
|
||||
func (d *Device) AddressBits() int { |
||||
val, _ := d.getInfoUint(C.CL_DEVICE_ADDRESS_BITS, true) |
||||
return int(val) |
||||
} |
||||
|
||||
// Size of global memory cache line in bytes.
|
||||
func (d *Device) GlobalMemCachelineSize() int { |
||||
val, _ := d.getInfoUint(C.CL_DEVICE_GLOBAL_MEM_CACHELINE_SIZE, true) |
||||
return int(val) |
||||
} |
||||
|
||||
// Maximum configured clock frequency of the device in MHz.
|
||||
func (d *Device) MaxClockFrequency() int { |
||||
val, _ := d.getInfoUint(C.CL_DEVICE_MAX_CLOCK_FREQUENCY, true) |
||||
return int(val) |
||||
} |
||||
|
||||
// The number of parallel compute units on the OpenCL device.
|
||||
// A work-group executes on a single compute unit. The minimum value is 1.
|
||||
func (d *Device) MaxComputeUnits() int { |
||||
val, _ := d.getInfoUint(C.CL_DEVICE_MAX_COMPUTE_UNITS, true) |
||||
return int(val) |
||||
} |
||||
|
||||
// Max number of arguments declared with the __constant qualifier in a kernel.
|
||||
// The minimum value is 8 for devices that are not of type CL_DEVICE_TYPE_CUSTOM.
|
||||
func (d *Device) MaxConstantArgs() int { |
||||
val, _ := d.getInfoUint(C.CL_DEVICE_MAX_CONSTANT_ARGS, true) |
||||
return int(val) |
||||
} |
||||
|
||||
// Max number of simultaneous image objects that can be read by a kernel.
|
||||
// The minimum value is 128 if CL_DEVICE_IMAGE_SUPPORT is CL_TRUE.
|
||||
func (d *Device) MaxReadImageArgs() int { |
||||
val, _ := d.getInfoUint(C.CL_DEVICE_MAX_READ_IMAGE_ARGS, true) |
||||
return int(val) |
||||
} |
||||
|
||||
// Maximum number of samplers that can be used in a kernel. The minimum
|
||||
// value is 16 if CL_DEVICE_IMAGE_SUPPORT is CL_TRUE. (Also see sampler_t.)
|
||||
func (d *Device) MaxSamplers() int { |
||||
val, _ := d.getInfoUint(C.CL_DEVICE_MAX_SAMPLERS, true) |
||||
return int(val) |
||||
} |
||||
|
||||
// Maximum dimensions that specify the global and local work-item IDs used
|
||||
// by the data parallel execution model. (Refer to clEnqueueNDRangeKernel).
|
||||
// The minimum value is 3 for devices that are not of type CL_DEVICE_TYPE_CUSTOM.
|
||||
func (d *Device) MaxWorkItemDimensions() int { |
||||
val, _ := d.getInfoUint(C.CL_DEVICE_MAX_WORK_ITEM_DIMENSIONS, true) |
||||
return int(val) |
||||
} |
||||
|
||||
// Max number of simultaneous image objects that can be written to by a
|
||||
// kernel. The minimum value is 8 if CL_DEVICE_IMAGE_SUPPORT is CL_TRUE.
|
||||
func (d *Device) MaxWriteImageArgs() int { |
||||
val, _ := d.getInfoUint(C.CL_DEVICE_MAX_WRITE_IMAGE_ARGS, true) |
||||
return int(val) |
||||
} |
||||
|
||||
// The minimum value is the size (in bits) of the largest OpenCL built-in
|
||||
// data type supported by the device (long16 in FULL profile, long16 or
|
||||
// int16 in EMBEDDED profile) for devices that are not of type CL_DEVICE_TYPE_CUSTOM.
|
||||
func (d *Device) MemBaseAddrAlign() int { |
||||
val, _ := d.getInfoUint(C.CL_DEVICE_MEM_BASE_ADDR_ALIGN, true) |
||||
return int(val) |
||||
} |
||||
|
||||
func (d *Device) NativeVectorWidthChar() int { |
||||
val, _ := d.getInfoUint(C.CL_DEVICE_NATIVE_VECTOR_WIDTH_CHAR, true) |
||||
return int(val) |
||||
} |
||||
|
||||
func (d *Device) NativeVectorWidthShort() int { |
||||
val, _ := d.getInfoUint(C.CL_DEVICE_NATIVE_VECTOR_WIDTH_SHORT, true) |
||||
return int(val) |
||||
} |
||||
|
||||
func (d *Device) NativeVectorWidthInt() int { |
||||
val, _ := d.getInfoUint(C.CL_DEVICE_NATIVE_VECTOR_WIDTH_INT, true) |
||||
return int(val) |
||||
} |
||||
|
||||
func (d *Device) NativeVectorWidthLong() int { |
||||
val, _ := d.getInfoUint(C.CL_DEVICE_NATIVE_VECTOR_WIDTH_LONG, true) |
||||
return int(val) |
||||
} |
||||
|
||||
func (d *Device) NativeVectorWidthFloat() int { |
||||
val, _ := d.getInfoUint(C.CL_DEVICE_NATIVE_VECTOR_WIDTH_FLOAT, true) |
||||
return int(val) |
||||
} |
||||
|
||||
func (d *Device) NativeVectorWidthDouble() int { |
||||
val, _ := d.getInfoUint(C.CL_DEVICE_NATIVE_VECTOR_WIDTH_DOUBLE, true) |
||||
return int(val) |
||||
} |
||||
|
||||
func (d *Device) NativeVectorWidthHalf() int { |
||||
val, _ := d.getInfoUint(C.CL_DEVICE_NATIVE_VECTOR_WIDTH_HALF, true) |
||||
return int(val) |
||||
} |
||||
|
||||
// Max height of 2D image in pixels. The minimum value is 8192
|
||||
// if CL_DEVICE_IMAGE_SUPPORT is CL_TRUE.
|
||||
func (d *Device) Image2DMaxHeight() int { |
||||
val, _ := d.getInfoSize(C.CL_DEVICE_IMAGE2D_MAX_HEIGHT, true) |
||||
return int(val) |
||||
} |
||||
|
||||
// Max width of 2D image or 1D image not created from a buffer object in
|
||||
// pixels. The minimum value is 8192 if CL_DEVICE_IMAGE_SUPPORT is CL_TRUE.
|
||||
func (d *Device) Image2DMaxWidth() int { |
||||
val, _ := d.getInfoSize(C.CL_DEVICE_IMAGE2D_MAX_WIDTH, true) |
||||
return int(val) |
||||
} |
||||
|
||||
// Max depth of 3D image in pixels. The minimum value is 2048 if CL_DEVICE_IMAGE_SUPPORT is CL_TRUE.
|
||||
func (d *Device) Image3DMaxDepth() int { |
||||
val, _ := d.getInfoSize(C.CL_DEVICE_IMAGE3D_MAX_DEPTH, true) |
||||
return int(val) |
||||
} |
||||
|
||||
// Max height of 3D image in pixels. The minimum value is 2048 if CL_DEVICE_IMAGE_SUPPORT is CL_TRUE.
|
||||
func (d *Device) Image3DMaxHeight() int { |
||||
val, _ := d.getInfoSize(C.CL_DEVICE_IMAGE3D_MAX_HEIGHT, true) |
||||
return int(val) |
||||
} |
||||
|
||||
// Max width of 3D image in pixels. The minimum value is 2048 if CL_DEVICE_IMAGE_SUPPORT is CL_TRUE.
|
||||
func (d *Device) Image3DMaxWidth() int { |
||||
val, _ := d.getInfoSize(C.CL_DEVICE_IMAGE3D_MAX_WIDTH, true) |
||||
return int(val) |
||||
} |
||||
|
||||
// Max size in bytes of the arguments that can be passed to a kernel. The
|
||||
// minimum value is 1024 for devices that are not of type CL_DEVICE_TYPE_CUSTOM.
|
||||
// For this minimum value, only a maximum of 128 arguments can be passed to a kernel.
|
||||
func (d *Device) MaxParameterSize() int { |
||||
val, _ := d.getInfoSize(C.CL_DEVICE_MAX_PARAMETER_SIZE, true) |
||||
return int(val) |
||||
} |
||||
|
||||
// Maximum number of work-items in a work-group executing a kernel on a
|
||||
// single compute unit, using the data parallel execution model. (Refer
|
||||
// to clEnqueueNDRangeKernel). The minimum value is 1.
|
||||
func (d *Device) MaxWorkGroupSize() int { |
||||
val, _ := d.getInfoSize(C.CL_DEVICE_MAX_WORK_GROUP_SIZE, true) |
||||
return int(val) |
||||
} |
||||
|
||||
// Describes the resolution of device timer. This is measured in nanoseconds.
|
||||
func (d *Device) ProfilingTimerResolution() int { |
||||
val, _ := d.getInfoSize(C.CL_DEVICE_PROFILING_TIMER_RESOLUTION, true) |
||||
return int(val) |
||||
} |
||||
|
||||
// Size of local memory arena in bytes. The minimum value is 32 KB for
|
||||
// devices that are not of type CL_DEVICE_TYPE_CUSTOM.
|
||||
func (d *Device) LocalMemSize() int64 { |
||||
val, _ := d.getInfoUlong(C.CL_DEVICE_LOCAL_MEM_SIZE, true) |
||||
return val |
||||
} |
||||
|
||||
// Max size in bytes of a constant buffer allocation. The minimum value is
|
||||
// 64 KB for devices that are not of type CL_DEVICE_TYPE_CUSTOM.
|
||||
func (d *Device) MaxConstantBufferSize() int64 { |
||||
val, _ := d.getInfoUlong(C.CL_DEVICE_MAX_CONSTANT_BUFFER_SIZE, true) |
||||
return val |
||||
} |
||||
|
||||
// Max size of memory object allocation in bytes. The minimum value is max
|
||||
// (1/4th of CL_DEVICE_GLOBAL_MEM_SIZE, 128*1024*1024) for devices that are
|
||||
// not of type CL_DEVICE_TYPE_CUSTOM.
|
||||
func (d *Device) MaxMemAllocSize() int64 { |
||||
val, _ := d.getInfoUlong(C.CL_DEVICE_MAX_MEM_ALLOC_SIZE, true) |
||||
return val |
||||
} |
||||
|
||||
// Size of global device memory in bytes.
|
||||
func (d *Device) GlobalMemSize() int64 { |
||||
val, _ := d.getInfoUlong(C.CL_DEVICE_GLOBAL_MEM_SIZE, true) |
||||
return val |
||||
} |
||||
|
||||
func (d *Device) Available() bool { |
||||
val, _ := d.getInfoBool(C.CL_DEVICE_AVAILABLE, true) |
||||
return val |
||||
} |
||||
|
||||
func (d *Device) CompilerAvailable() bool { |
||||
val, _ := d.getInfoBool(C.CL_DEVICE_COMPILER_AVAILABLE, true) |
||||
return val |
||||
} |
||||
|
||||
func (d *Device) EndianLittle() bool { |
||||
val, _ := d.getInfoBool(C.CL_DEVICE_ENDIAN_LITTLE, true) |
||||
return val |
||||
} |
||||
|
||||
// Is CL_TRUE if the device implements error correction for all
|
||||
// accesses to compute device memory (global and constant). Is
|
||||
// CL_FALSE if the device does not implement such error correction.
|
||||
func (d *Device) ErrorCorrectionSupport() bool { |
||||
val, _ := d.getInfoBool(C.CL_DEVICE_ERROR_CORRECTION_SUPPORT, true) |
||||
return val |
||||
} |
||||
|
||||
func (d *Device) HostUnifiedMemory() bool { |
||||
val, _ := d.getInfoBool(C.CL_DEVICE_HOST_UNIFIED_MEMORY, true) |
||||
return val |
||||
} |
||||
|
||||
func (d *Device) ImageSupport() bool { |
||||
val, _ := d.getInfoBool(C.CL_DEVICE_IMAGE_SUPPORT, true) |
||||
return val |
||||
} |
||||
|
||||
func (d *Device) Type() DeviceType { |
||||
var deviceType C.cl_device_type |
||||
if err := C.clGetDeviceInfo(d.id, C.CL_DEVICE_TYPE, C.size_t(unsafe.Sizeof(deviceType)), unsafe.Pointer(&deviceType), nil); err != C.CL_SUCCESS { |
||||
panic("Failed to get device type") |
||||
} |
||||
return DeviceType(deviceType) |
||||
} |
||||
|
||||
// Describes double precision floating-point capability of the OpenCL device
|
||||
func (d *Device) DoubleFPConfig() FPConfig { |
||||
var fpConfig C.cl_device_fp_config |
||||
if err := C.clGetDeviceInfo(d.id, C.CL_DEVICE_DOUBLE_FP_CONFIG, C.size_t(unsafe.Sizeof(fpConfig)), unsafe.Pointer(&fpConfig), nil); err != C.CL_SUCCESS { |
||||
panic("Failed to get double FP config") |
||||
} |
||||
return FPConfig(fpConfig) |
||||
} |
||||
|
||||
// Describes the OPTIONAL half precision floating-point capability of the OpenCL device
|
||||
func (d *Device) HalfFPConfig() FPConfig { |
||||
var fpConfig C.cl_device_fp_config |
||||
err := C.clGetDeviceInfo(d.id, C.CL_DEVICE_HALF_FP_CONFIG, C.size_t(unsafe.Sizeof(fpConfig)), unsafe.Pointer(&fpConfig), nil) |
||||
if err != C.CL_SUCCESS { |
||||
return FPConfig(0) |
||||
} |
||||
return FPConfig(fpConfig) |
||||
} |
||||
|
||||
// Type of local memory supported. This can be set to CL_LOCAL implying dedicated
|
||||
// local memory storage such as SRAM, or CL_GLOBAL. For custom devices, CL_NONE
|
||||
// can also be returned indicating no local memory support.
|
||||
func (d *Device) LocalMemType() LocalMemType { |
||||
var memType C.cl_device_local_mem_type |
||||
if err := C.clGetDeviceInfo(d.id, C.CL_DEVICE_LOCAL_MEM_TYPE, C.size_t(unsafe.Sizeof(memType)), unsafe.Pointer(&memType), nil); err != C.CL_SUCCESS { |
||||
return LocalMemType(C.CL_NONE) |
||||
} |
||||
return LocalMemType(memType) |
||||
} |
||||
|
||||
// Describes the execution capabilities of the device. The mandated minimum capability is CL_EXEC_KERNEL.
|
||||
func (d *Device) ExecutionCapabilities() ExecCapability { |
||||
var execCap C.cl_device_exec_capabilities |
||||
if err := C.clGetDeviceInfo(d.id, C.CL_DEVICE_EXECUTION_CAPABILITIES, C.size_t(unsafe.Sizeof(execCap)), unsafe.Pointer(&execCap), nil); err != C.CL_SUCCESS { |
||||
panic("Failed to get execution capabilities") |
||||
} |
||||
return ExecCapability(execCap) |
||||
} |
||||
|
||||
func (d *Device) GlobalMemCacheType() MemCacheType { |
||||
var memType C.cl_device_mem_cache_type |
||||
if err := C.clGetDeviceInfo(d.id, C.CL_DEVICE_GLOBAL_MEM_CACHE_TYPE, C.size_t(unsafe.Sizeof(memType)), unsafe.Pointer(&memType), nil); err != C.CL_SUCCESS { |
||||
return MemCacheType(C.CL_NONE) |
||||
} |
||||
return MemCacheType(memType) |
||||
} |
||||
|
||||
// Maximum number of work-items that can be specified in each dimension of the work-group to clEnqueueNDRangeKernel.
|
||||
//
|
||||
// Returns n size_t entries, where n is the value returned by the query for CL_DEVICE_MAX_WORK_ITEM_DIMENSIONS.
|
||||
//
|
||||
// The minimum value is (1, 1, 1) for devices that are not of type CL_DEVICE_TYPE_CUSTOM.
|
||||
func (d *Device) MaxWorkItemSizes() []int { |
||||
dims := d.MaxWorkItemDimensions() |
||||
sizes := make([]C.size_t, dims) |
||||
if err := C.clGetDeviceInfo(d.id, C.CL_DEVICE_MAX_WORK_ITEM_SIZES, C.size_t(int(unsafe.Sizeof(sizes[0]))*dims), unsafe.Pointer(&sizes[0]), nil); err != C.CL_SUCCESS { |
||||
panic("Failed to get max work item sizes") |
||||
} |
||||
intSizes := make([]int, dims) |
||||
for i, s := range sizes { |
||||
intSizes[i] = int(s) |
||||
} |
||||
return intSizes |
||||
} |
@ -0,0 +1,51 @@ |
||||
// +build cl12
|
||||
|
||||
package cl |
||||
|
||||
// #ifdef __APPLE__
|
||||
// #include "OpenCL/opencl.h"
|
||||
// #else
|
||||
// #include "cl.h"
|
||||
// #endif
|
||||
import "C" |
||||
import "unsafe" |
||||
|
||||
const FPConfigCorrectlyRoundedDivideSqrt FPConfig = C.CL_FP_CORRECTLY_ROUNDED_DIVIDE_SQRT |
||||
|
||||
func init() { |
||||
fpConfigNameMap[FPConfigCorrectlyRoundedDivideSqrt] = "CorrectlyRoundedDivideSqrt" |
||||
} |
||||
|
||||
func (d *Device) BuiltInKernels() string { |
||||
str, _ := d.getInfoString(C.CL_DEVICE_BUILT_IN_KERNELS, true) |
||||
return str |
||||
} |
||||
|
||||
// Is CL_FALSE if the implementation does not have a linker available. Is CL_TRUE if the linker is available. This can be CL_FALSE for the embedded platform profile only. This must be CL_TRUE if CL_DEVICE_COMPILER_AVAILABLE is CL_TRUE
|
||||
func (d *Device) LinkerAvailable() bool { |
||||
val, _ := d.getInfoBool(C.CL_DEVICE_LINKER_AVAILABLE, true) |
||||
return val |
||||
} |
||||
|
||||
func (d *Device) ParentDevice() *Device { |
||||
var deviceId C.cl_device_id |
||||
if err := C.clGetDeviceInfo(d.id, C.CL_DEVICE_PARENT_DEVICE, C.size_t(unsafe.Sizeof(deviceId)), unsafe.Pointer(&deviceId), nil); err != C.CL_SUCCESS { |
||||
panic("ParentDevice failed") |
||||
} |
||||
if deviceId == nil { |
||||
return nil |
||||
} |
||||
return &Device{id: deviceId} |
||||
} |
||||
|
||||
// Max number of pixels for a 1D image created from a buffer object. The minimum value is 65536 if CL_DEVICE_IMAGE_SUPPORT is CL_TRUE.
|
||||
func (d *Device) ImageMaxBufferSize() int { |
||||
val, _ := d.getInfoSize(C.CL_DEVICE_IMAGE_MAX_BUFFER_SIZE, true) |
||||
return int(val) |
||||
} |
||||
|
||||
// Max number of images in a 1D or 2D image array. The minimum value is 2048 if CL_DEVICE_IMAGE_SUPPORT is CL_TRUE
|
||||
func (d *Device) ImageMaxArraySize() int { |
||||
val, _ := d.getInfoSize(C.CL_DEVICE_IMAGE_MAX_ARRAY_SIZE, true) |
||||
return int(val) |
||||
} |
1210
Godeps/_workspace/src/github.com/Gustav-Simonsson/go-opencl/cl/headers/1.2/cl.h
generated
vendored
1210
Godeps/_workspace/src/github.com/Gustav-Simonsson/go-opencl/cl/headers/1.2/cl.h
generated
vendored
File diff suppressed because it is too large
Load Diff
315
Godeps/_workspace/src/github.com/Gustav-Simonsson/go-opencl/cl/headers/1.2/cl_ext.h
generated
vendored
315
Godeps/_workspace/src/github.com/Gustav-Simonsson/go-opencl/cl/headers/1.2/cl_ext.h
generated
vendored
@ -0,0 +1,315 @@ |
||||
/*******************************************************************************
|
||||
* Copyright (c) 2008-2013 The Khronos Group Inc. |
||||
* |
||||
* Permission is hereby granted, free of charge, to any person obtaining a |
||||
* copy of this software and/or associated documentation files (the |
||||
* "Materials"), to deal in the Materials without restriction, including |
||||
* without limitation the rights to use, copy, modify, merge, publish, |
||||
* distribute, sublicense, and/or sell copies of the Materials, and to |
||||
* permit persons to whom the Materials are furnished to do so, subject to |
||||
* the following conditions: |
||||
* |
||||
* The above copyright notice and this permission notice shall be included |
||||
* in all copies or substantial portions of the Materials. |
||||
* |
||||
* THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. |
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY |
||||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, |
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE |
||||
* MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. |
||||
******************************************************************************/ |
||||
|
||||
/* $Revision: 11928 $ on $Date: 2010-07-13 09:04:56 -0700 (Tue, 13 Jul 2010) $ */ |
||||
|
||||
/* cl_ext.h contains OpenCL extensions which don't have external */ |
||||
/* (OpenGL, D3D) dependencies. */ |
||||
|
||||
#ifndef __CL_EXT_H |
||||
#define __CL_EXT_H |
||||
|
||||
#ifdef __cplusplus |
||||
extern "C" { |
||||
#endif |
||||
|
||||
#ifdef __APPLE__ |
||||
#include <AvailabilityMacros.h> |
||||
#endif |
||||
|
||||
#include <cl.h> |
||||
|
||||
/* cl_khr_fp16 extension - no extension #define since it has no functions */ |
||||
#define CL_DEVICE_HALF_FP_CONFIG 0x1033 |
||||
|
||||
/* Memory object destruction
|
||||
* |
||||
* Apple extension for use to manage externally allocated buffers used with cl_mem objects with CL_MEM_USE_HOST_PTR |
||||
* |
||||
* Registers a user callback function that will be called when the memory object is deleted and its resources
|
||||
* freed. Each call to clSetMemObjectCallbackFn registers the specified user callback function on a callback
|
||||
* stack associated with memobj. The registered user callback functions are called in the reverse order in
|
||||
* which they were registered. The user callback functions are called and then the memory object is deleted
|
||||
* and its resources freed. This provides a mechanism for the application (and libraries) using memobj to be
|
||||
* notified when the memory referenced by host_ptr, specified when the memory object is created and used as
|
||||
* the storage bits for the memory object, can be reused or freed. |
||||
* |
||||
* The application may not call CL api's with the cl_mem object passed to the pfn_notify. |
||||
* |
||||
* Please check for the "cl_APPLE_SetMemObjectDestructor" extension using clGetDeviceInfo(CL_DEVICE_EXTENSIONS) |
||||
* before using. |
||||
*/ |
||||
#define cl_APPLE_SetMemObjectDestructor 1 |
||||
cl_int CL_API_ENTRY clSetMemObjectDestructorAPPLE( cl_mem /* memobj */,
|
||||
void (* /*pfn_notify*/)( cl_mem /* memobj */, void* /*user_data*/),
|
||||
void * /*user_data */ ) CL_EXT_SUFFIX__VERSION_1_0;
|
||||
|
||||
|
||||
/* Context Logging Functions
|
||||
* |
||||
* The next three convenience functions are intended to be used as the pfn_notify parameter to clCreateContext(). |
||||
* Please check for the "cl_APPLE_ContextLoggingFunctions" extension using clGetDeviceInfo(CL_DEVICE_EXTENSIONS) |
||||
* before using. |
||||
* |
||||
* clLogMessagesToSystemLog fowards on all log messages to the Apple System Logger
|
||||
*/ |
||||
#define cl_APPLE_ContextLoggingFunctions 1 |
||||
extern void CL_API_ENTRY clLogMessagesToSystemLogAPPLE( const char * /* errstr */,
|
||||
const void * /* private_info */,
|
||||
size_t /* cb */,
|
||||
void * /* user_data */ ) CL_EXT_SUFFIX__VERSION_1_0; |
||||
|
||||
/* clLogMessagesToStdout sends all log messages to the file descriptor stdout */ |
||||
extern void CL_API_ENTRY clLogMessagesToStdoutAPPLE( const char * /* errstr */,
|
||||
const void * /* private_info */,
|
||||
size_t /* cb */,
|
||||
void * /* user_data */ ) CL_EXT_SUFFIX__VERSION_1_0; |
||||
|
||||
/* clLogMessagesToStderr sends all log messages to the file descriptor stderr */ |
||||
extern void CL_API_ENTRY clLogMessagesToStderrAPPLE( const char * /* errstr */,
|
||||
const void * /* private_info */,
|
||||
size_t /* cb */,
|
||||
void * /* user_data */ ) CL_EXT_SUFFIX__VERSION_1_0; |
||||
|
||||
|
||||
/************************
|
||||
* cl_khr_icd extension *
|
||||
************************/ |
||||
#define cl_khr_icd 1 |
||||
|
||||
/* cl_platform_info */ |
||||
#define CL_PLATFORM_ICD_SUFFIX_KHR 0x0920 |
||||
|
||||
/* Additional Error Codes */ |
||||
#define CL_PLATFORM_NOT_FOUND_KHR -1001 |
||||
|
||||
extern CL_API_ENTRY cl_int CL_API_CALL |
||||
clIcdGetPlatformIDsKHR(cl_uint /* num_entries */, |
||||
cl_platform_id * /* platforms */, |
||||
cl_uint * /* num_platforms */); |
||||
|
||||
typedef CL_API_ENTRY cl_int (CL_API_CALL *clIcdGetPlatformIDsKHR_fn)( |
||||
cl_uint /* num_entries */, |
||||
cl_platform_id * /* platforms */, |
||||
cl_uint * /* num_platforms */); |
||||
|
||||
|
||||
/* Extension: cl_khr_image2D_buffer
|
||||
* |
||||
* This extension allows a 2D image to be created from a cl_mem buffer without a copy. |
||||
* The type associated with a 2D image created from a buffer in an OpenCL program is image2d_t. |
||||
* Both the sampler and sampler-less read_image built-in functions are supported for 2D images |
||||
* and 2D images created from a buffer. Similarly, the write_image built-ins are also supported |
||||
* for 2D images created from a buffer. |
||||
* |
||||
* When the 2D image from buffer is created, the client must specify the width, |
||||
* height, image format (i.e. channel order and channel data type) and optionally the row pitch |
||||
* |
||||
* The pitch specified must be a multiple of CL_DEVICE_IMAGE_PITCH_ALIGNMENT pixels. |
||||
* The base address of the buffer must be aligned to CL_DEVICE_IMAGE_BASE_ADDRESS_ALIGNMENT pixels. |
||||
*/ |
||||
|
||||
/*************************************
|
||||
* cl_khr_initalize_memory extension * |
||||
*************************************/ |
||||
|
||||
#define CL_CONTEXT_MEMORY_INITIALIZE_KHR 0x200E |
||||
|
||||
|
||||
/**************************************
|
||||
* cl_khr_terminate_context extension * |
||||
**************************************/ |
||||
|
||||
#define CL_DEVICE_TERMINATE_CAPABILITY_KHR 0x200F |
||||
#define CL_CONTEXT_TERMINATE_KHR 0x2010 |
||||
|
||||
#define cl_khr_terminate_context 1 |
||||
extern CL_API_ENTRY cl_int CL_API_CALL clTerminateContextKHR(cl_context /* context */) CL_EXT_SUFFIX__VERSION_1_2; |
||||
|
||||
typedef CL_API_ENTRY cl_int (CL_API_CALL *clTerminateContextKHR_fn)(cl_context /* context */) CL_EXT_SUFFIX__VERSION_1_2; |
||||
|
||||
|
||||
/*
|
||||
* Extension: cl_khr_spir |
||||
* |
||||
* This extension adds support to create an OpenCL program object from a
|
||||
* Standard Portable Intermediate Representation (SPIR) instance |
||||
*/ |
||||
|
||||
#define CL_DEVICE_SPIR_VERSIONS 0x40E0 |
||||
#define CL_PROGRAM_BINARY_TYPE_INTERMEDIATE 0x40E1 |
||||
|
||||
|
||||
/******************************************
|
||||
* cl_nv_device_attribute_query extension * |
||||
******************************************/ |
||||
/* cl_nv_device_attribute_query extension - no extension #define since it has no functions */ |
||||
#define CL_DEVICE_COMPUTE_CAPABILITY_MAJOR_NV 0x4000 |
||||
#define CL_DEVICE_COMPUTE_CAPABILITY_MINOR_NV 0x4001 |
||||
#define CL_DEVICE_REGISTERS_PER_BLOCK_NV 0x4002 |
||||
#define CL_DEVICE_WARP_SIZE_NV 0x4003 |
||||
#define CL_DEVICE_GPU_OVERLAP_NV 0x4004 |
||||
#define CL_DEVICE_KERNEL_EXEC_TIMEOUT_NV 0x4005 |
||||
#define CL_DEVICE_INTEGRATED_MEMORY_NV 0x4006 |
||||
|
||||
/*********************************
|
||||
* cl_amd_device_attribute_query * |
||||
*********************************/ |
||||
#define CL_DEVICE_PROFILING_TIMER_OFFSET_AMD 0x4036 |
||||
|
||||
/*********************************
|
||||
* cl_arm_printf extension |
||||
*********************************/ |
||||
#define CL_PRINTF_CALLBACK_ARM 0x40B0 |
||||
#define CL_PRINTF_BUFFERSIZE_ARM 0x40B1 |
||||
|
||||
#ifdef CL_VERSION_1_1 |
||||
/***********************************
|
||||
* cl_ext_device_fission extension * |
||||
***********************************/ |
||||
#define cl_ext_device_fission 1 |
||||
|
||||
extern CL_API_ENTRY cl_int CL_API_CALL |
||||
clReleaseDeviceEXT( cl_device_id /*device*/ ) CL_EXT_SUFFIX__VERSION_1_1;
|
||||
|
||||
typedef CL_API_ENTRY cl_int
|
||||
(CL_API_CALL *clReleaseDeviceEXT_fn)( cl_device_id /*device*/ ) CL_EXT_SUFFIX__VERSION_1_1; |
||||
|
||||
extern CL_API_ENTRY cl_int CL_API_CALL |
||||
clRetainDeviceEXT( cl_device_id /*device*/ ) CL_EXT_SUFFIX__VERSION_1_1;
|
||||
|
||||
typedef CL_API_ENTRY cl_int
|
||||
(CL_API_CALL *clRetainDeviceEXT_fn)( cl_device_id /*device*/ ) CL_EXT_SUFFIX__VERSION_1_1; |
||||
|
||||
typedef cl_ulong cl_device_partition_property_ext; |
||||
extern CL_API_ENTRY cl_int CL_API_CALL |
||||
clCreateSubDevicesEXT( cl_device_id /*in_device*/, |
||||
const cl_device_partition_property_ext * /* properties */, |
||||
cl_uint /*num_entries*/, |
||||
cl_device_id * /*out_devices*/, |
||||
cl_uint * /*num_devices*/ ) CL_EXT_SUFFIX__VERSION_1_1; |
||||
|
||||
typedef CL_API_ENTRY cl_int
|
||||
( CL_API_CALL * clCreateSubDevicesEXT_fn)( cl_device_id /*in_device*/, |
||||
const cl_device_partition_property_ext * /* properties */, |
||||
cl_uint /*num_entries*/, |
||||
cl_device_id * /*out_devices*/, |
||||
cl_uint * /*num_devices*/ ) CL_EXT_SUFFIX__VERSION_1_1; |
||||
|
||||
/* cl_device_partition_property_ext */ |
||||
#define CL_DEVICE_PARTITION_EQUALLY_EXT 0x4050 |
||||
#define CL_DEVICE_PARTITION_BY_COUNTS_EXT 0x4051 |
||||
#define CL_DEVICE_PARTITION_BY_NAMES_EXT 0x4052 |
||||
#define CL_DEVICE_PARTITION_BY_AFFINITY_DOMAIN_EXT 0x4053 |
||||
|
||||
/* clDeviceGetInfo selectors */ |
||||
#define CL_DEVICE_PARENT_DEVICE_EXT 0x4054 |
||||
#define CL_DEVICE_PARTITION_TYPES_EXT 0x4055 |
||||
#define CL_DEVICE_AFFINITY_DOMAINS_EXT 0x4056 |
||||
#define CL_DEVICE_REFERENCE_COUNT_EXT 0x4057 |
||||
#define CL_DEVICE_PARTITION_STYLE_EXT 0x4058 |
||||
|
||||
/* error codes */ |
||||
#define CL_DEVICE_PARTITION_FAILED_EXT -1057 |
||||
#define CL_INVALID_PARTITION_COUNT_EXT -1058 |
||||
#define CL_INVALID_PARTITION_NAME_EXT -1059 |
||||
|
||||
/* CL_AFFINITY_DOMAINs */ |
||||
#define CL_AFFINITY_DOMAIN_L1_CACHE_EXT 0x1 |
||||
#define CL_AFFINITY_DOMAIN_L2_CACHE_EXT 0x2 |
||||
#define CL_AFFINITY_DOMAIN_L3_CACHE_EXT 0x3 |
||||
#define CL_AFFINITY_DOMAIN_L4_CACHE_EXT 0x4 |
||||
#define CL_AFFINITY_DOMAIN_NUMA_EXT 0x10 |
||||
#define CL_AFFINITY_DOMAIN_NEXT_FISSIONABLE_EXT 0x100 |
||||
|
||||
/* cl_device_partition_property_ext list terminators */ |
||||
#define CL_PROPERTIES_LIST_END_EXT ((cl_device_partition_property_ext) 0) |
||||
#define CL_PARTITION_BY_COUNTS_LIST_END_EXT ((cl_device_partition_property_ext) 0) |
||||
#define CL_PARTITION_BY_NAMES_LIST_END_EXT ((cl_device_partition_property_ext) 0 - 1) |
||||
|
||||
/*********************************
|
||||
* cl_qcom_ext_host_ptr extension |
||||
*********************************/ |
||||
|
||||
#define CL_MEM_EXT_HOST_PTR_QCOM (1 << 29) |
||||
|
||||
#define CL_DEVICE_EXT_MEM_PADDING_IN_BYTES_QCOM 0x40A0 |
||||
#define CL_DEVICE_PAGE_SIZE_QCOM 0x40A1 |
||||
#define CL_IMAGE_ROW_ALIGNMENT_QCOM 0x40A2 |
||||
#define CL_IMAGE_SLICE_ALIGNMENT_QCOM 0x40A3 |
||||
#define CL_MEM_HOST_UNCACHED_QCOM 0x40A4 |
||||
#define CL_MEM_HOST_WRITEBACK_QCOM 0x40A5 |
||||
#define CL_MEM_HOST_WRITETHROUGH_QCOM 0x40A6 |
||||
#define CL_MEM_HOST_WRITE_COMBINING_QCOM 0x40A7 |
||||
|
||||
typedef cl_uint cl_image_pitch_info_qcom; |
||||
|
||||
extern CL_API_ENTRY cl_int CL_API_CALL |
||||
clGetDeviceImageInfoQCOM(cl_device_id device, |
||||
size_t image_width, |
||||
size_t image_height, |
||||
const cl_image_format *image_format, |
||||
cl_image_pitch_info_qcom param_name, |
||||
size_t param_value_size, |
||||
void *param_value, |
||||
size_t *param_value_size_ret); |
||||
|
||||
typedef struct _cl_mem_ext_host_ptr |
||||
{ |
||||
/* Type of external memory allocation. */ |
||||
/* Legal values will be defined in layered extensions. */ |
||||
cl_uint allocation_type; |
||||
|
||||
/* Host cache policy for this external memory allocation. */ |
||||
cl_uint host_cache_policy; |
||||
|
||||
} cl_mem_ext_host_ptr; |
||||
|
||||
/*********************************
|
||||
* cl_qcom_ion_host_ptr extension |
||||
*********************************/ |
||||
|
||||
#define CL_MEM_ION_HOST_PTR_QCOM 0x40A8 |
||||
|
||||
typedef struct _cl_mem_ion_host_ptr |
||||
{ |
||||
/* Type of external memory allocation. */ |
||||
/* Must be CL_MEM_ION_HOST_PTR_QCOM for ION allocations. */ |
||||
cl_mem_ext_host_ptr ext_host_ptr; |
||||
|
||||
/* ION file descriptor */ |
||||
int ion_filedesc; |
||||
|
||||
/* Host pointer to the ION allocated memory */ |
||||
void* ion_hostptr; |
||||
|
||||
} cl_mem_ion_host_ptr; |
||||
|
||||
#endif /* CL_VERSION_1_1 */ |
||||
|
||||
#ifdef __cplusplus |
||||
} |
||||
#endif |
||||
|
||||
|
||||
#endif /* __CL_EXT_H */ |
158
Godeps/_workspace/src/github.com/Gustav-Simonsson/go-opencl/cl/headers/1.2/cl_gl.h
generated
vendored
158
Godeps/_workspace/src/github.com/Gustav-Simonsson/go-opencl/cl/headers/1.2/cl_gl.h
generated
vendored
@ -0,0 +1,158 @@ |
||||
/**********************************************************************************
|
||||
* Copyright (c) 2008 - 2012 The Khronos Group Inc. |
||||
* |
||||
* Permission is hereby granted, free of charge, to any person obtaining a |
||||
* copy of this software and/or associated documentation files (the |
||||
* "Materials"), to deal in the Materials without restriction, including |
||||
* without limitation the rights to use, copy, modify, merge, publish, |
||||
* distribute, sublicense, and/or sell copies of the Materials, and to |
||||
* permit persons to whom the Materials are furnished to do so, subject to |
||||
* the following conditions: |
||||
* |
||||
* The above copyright notice and this permission notice shall be included |
||||
* in all copies or substantial portions of the Materials. |
||||
* |
||||
* THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. |
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY |
||||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, |
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE |
||||
* MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. |
||||
**********************************************************************************/ |
||||
|
||||
#ifndef __OPENCL_CL_GL_H |
||||
#define __OPENCL_CL_GL_H |
||||
|
||||
#include <cl.h> |
||||
|
||||
#ifdef __cplusplus |
||||
extern "C" { |
||||
#endif |
||||
|
||||
typedef cl_uint cl_gl_object_type; |
||||
typedef cl_uint cl_gl_texture_info; |
||||
typedef cl_uint cl_gl_platform_info; |
||||
typedef struct __GLsync *cl_GLsync; |
||||
|
||||
/* cl_gl_object_type = 0x2000 - 0x200F enum values are currently taken */ |
||||
#define CL_GL_OBJECT_BUFFER 0x2000 |
||||
#define CL_GL_OBJECT_TEXTURE2D 0x2001 |
||||
#define CL_GL_OBJECT_TEXTURE3D 0x2002 |
||||
#define CL_GL_OBJECT_RENDERBUFFER 0x2003 |
||||
#define CL_GL_OBJECT_TEXTURE2D_ARRAY 0x200E |
||||
#define CL_GL_OBJECT_TEXTURE1D 0x200F |
||||
#define CL_GL_OBJECT_TEXTURE1D_ARRAY 0x2010 |
||||
#define CL_GL_OBJECT_TEXTURE_BUFFER 0x2011 |
||||
|
||||
/* cl_gl_texture_info */ |
||||
#define CL_GL_TEXTURE_TARGET 0x2004 |
||||
#define CL_GL_MIPMAP_LEVEL 0x2005 |
||||
#define CL_GL_NUM_SAMPLES 0x2012 |
||||
|
||||
|
||||
extern CL_API_ENTRY cl_mem CL_API_CALL |
||||
clCreateFromGLBuffer(cl_context /* context */, |
||||
cl_mem_flags /* flags */, |
||||
cl_GLuint /* bufobj */, |
||||
int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; |
||||
|
||||
extern CL_API_ENTRY cl_mem CL_API_CALL |
||||
clCreateFromGLTexture(cl_context /* context */, |
||||
cl_mem_flags /* flags */, |
||||
cl_GLenum /* target */, |
||||
cl_GLint /* miplevel */, |
||||
cl_GLuint /* texture */, |
||||
cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_2; |
||||
|
||||
extern CL_API_ENTRY cl_mem CL_API_CALL |
||||
clCreateFromGLRenderbuffer(cl_context /* context */, |
||||
cl_mem_flags /* flags */, |
||||
cl_GLuint /* renderbuffer */, |
||||
cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; |
||||
|
||||
extern CL_API_ENTRY cl_int CL_API_CALL |
||||
clGetGLObjectInfo(cl_mem /* memobj */, |
||||
cl_gl_object_type * /* gl_object_type */, |
||||
cl_GLuint * /* gl_object_name */) CL_API_SUFFIX__VERSION_1_0; |
||||
|
||||
extern CL_API_ENTRY cl_int CL_API_CALL |
||||
clGetGLTextureInfo(cl_mem /* memobj */, |
||||
cl_gl_texture_info /* param_name */, |
||||
size_t /* param_value_size */, |
||||
void * /* param_value */, |
||||
size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; |
||||
|
||||
extern CL_API_ENTRY cl_int CL_API_CALL |
||||
clEnqueueAcquireGLObjects(cl_command_queue /* command_queue */, |
||||
cl_uint /* num_objects */, |
||||
const cl_mem * /* mem_objects */, |
||||
cl_uint /* num_events_in_wait_list */, |
||||
const cl_event * /* event_wait_list */, |
||||
cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; |
||||
|
||||
extern CL_API_ENTRY cl_int CL_API_CALL |
||||
clEnqueueReleaseGLObjects(cl_command_queue /* command_queue */, |
||||
cl_uint /* num_objects */, |
||||
const cl_mem * /* mem_objects */, |
||||
cl_uint /* num_events_in_wait_list */, |
||||
const cl_event * /* event_wait_list */, |
||||
cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; |
||||
|
||||
|
||||
/* Deprecated OpenCL 1.1 APIs */ |
||||
extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_mem CL_API_CALL |
||||
clCreateFromGLTexture2D(cl_context /* context */, |
||||
cl_mem_flags /* flags */, |
||||
cl_GLenum /* target */, |
||||
cl_GLint /* miplevel */, |
||||
cl_GLuint /* texture */, |
||||
cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; |
||||
|
||||
extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_mem CL_API_CALL |
||||
clCreateFromGLTexture3D(cl_context /* context */, |
||||
cl_mem_flags /* flags */, |
||||
cl_GLenum /* target */, |
||||
cl_GLint /* miplevel */, |
||||
cl_GLuint /* texture */, |
||||
cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; |
||||
|
||||
/* cl_khr_gl_sharing extension */ |
||||
|
||||
#define cl_khr_gl_sharing 1 |
||||
|
||||
typedef cl_uint cl_gl_context_info; |
||||
|
||||
/* Additional Error Codes */ |
||||
#define CL_INVALID_GL_SHAREGROUP_REFERENCE_KHR -1000 |
||||
|
||||
/* cl_gl_context_info */ |
||||
#define CL_CURRENT_DEVICE_FOR_GL_CONTEXT_KHR 0x2006 |
||||
#define CL_DEVICES_FOR_GL_CONTEXT_KHR 0x2007 |
||||
|
||||
/* Additional cl_context_properties */ |
||||
#define CL_GL_CONTEXT_KHR 0x2008 |
||||
#define CL_EGL_DISPLAY_KHR 0x2009 |
||||
#define CL_GLX_DISPLAY_KHR 0x200A |
||||
#define CL_WGL_HDC_KHR 0x200B |
||||
#define CL_CGL_SHAREGROUP_KHR 0x200C |
||||
|
||||
extern CL_API_ENTRY cl_int CL_API_CALL |
||||
clGetGLContextInfoKHR(const cl_context_properties * /* properties */, |
||||
cl_gl_context_info /* param_name */, |
||||
size_t /* param_value_size */, |
||||
void * /* param_value */, |
||||
size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; |
||||
|
||||
typedef CL_API_ENTRY cl_int (CL_API_CALL *clGetGLContextInfoKHR_fn)( |
||||
const cl_context_properties * properties, |
||||
cl_gl_context_info param_name, |
||||
size_t param_value_size, |
||||
void * param_value, |
||||
size_t * param_value_size_ret); |
||||
|
||||
#ifdef __cplusplus |
||||
} |
||||
#endif |
||||
|
||||
#endif /* __OPENCL_CL_GL_H */ |
65
Godeps/_workspace/src/github.com/Gustav-Simonsson/go-opencl/cl/headers/1.2/cl_gl_ext.h
generated
vendored
65
Godeps/_workspace/src/github.com/Gustav-Simonsson/go-opencl/cl/headers/1.2/cl_gl_ext.h
generated
vendored
@ -0,0 +1,65 @@ |
||||
/**********************************************************************************
|
||||
* Copyright (c) 2008-2012 The Khronos Group Inc. |
||||
* |
||||
* Permission is hereby granted, free of charge, to any person obtaining a |
||||
* copy of this software and/or associated documentation files (the |
||||
* "Materials"), to deal in the Materials without restriction, including |
||||
* without limitation the rights to use, copy, modify, merge, publish, |
||||
* distribute, sublicense, and/or sell copies of the Materials, and to |
||||
* permit persons to whom the Materials are furnished to do so, subject to |
||||
* the following conditions: |
||||
* |
||||
* The above copyright notice and this permission notice shall be included |
||||
* in all copies or substantial portions of the Materials. |
||||
* |
||||
* THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. |
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY |
||||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, |
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE |
||||
* MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. |
||||
**********************************************************************************/ |
||||
|
||||
/* $Revision: 11708 $ on $Date: 2010-06-13 23:36:24 -0700 (Sun, 13 Jun 2010) $ */ |
||||
|
||||
/* cl_gl_ext.h contains vendor (non-KHR) OpenCL extensions which have */ |
||||
/* OpenGL dependencies. */ |
||||
|
||||
#ifndef __OPENCL_CL_GL_EXT_H |
||||
#define __OPENCL_CL_GL_EXT_H |
||||
|
||||
#ifdef __cplusplus |
||||
extern "C" { |
||||
#endif |
||||
|
||||
#include <cl_gl.h> |
||||
|
||||
/*
|
||||
* For each extension, follow this template |
||||
* cl_VEN_extname extension */ |
||||
/* #define cl_VEN_extname 1
|
||||
* ... define new types, if any |
||||
* ... define new tokens, if any |
||||
* ... define new APIs, if any |
||||
* |
||||
* If you need GLtypes here, mirror them with a cl_GLtype, rather than including a GL header |
||||
* This allows us to avoid having to decide whether to include GL headers or GLES here. |
||||
*/ |
||||
|
||||
/*
|
||||
* cl_khr_gl_event extension |
||||
* See section 9.9 in the OpenCL 1.1 spec for more information |
||||
*/ |
||||
#define CL_COMMAND_GL_FENCE_SYNC_OBJECT_KHR 0x200D |
||||
|
||||
extern CL_API_ENTRY cl_event CL_API_CALL |
||||
clCreateEventFromGLsyncKHR(cl_context /* context */, |
||||
cl_GLsync /* cl_GLsync */, |
||||
cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_1; |
||||
|
||||
#ifdef __cplusplus |
||||
} |
||||
#endif |
||||
|
||||
#endif /* __OPENCL_CL_GL_EXT_H */ |
1278
Godeps/_workspace/src/github.com/Gustav-Simonsson/go-opencl/cl/headers/1.2/cl_platform.h
generated
vendored
1278
Godeps/_workspace/src/github.com/Gustav-Simonsson/go-opencl/cl/headers/1.2/cl_platform.h
generated
vendored
File diff suppressed because it is too large
Load Diff
43
Godeps/_workspace/src/github.com/Gustav-Simonsson/go-opencl/cl/headers/1.2/opencl.h
generated
vendored
43
Godeps/_workspace/src/github.com/Gustav-Simonsson/go-opencl/cl/headers/1.2/opencl.h
generated
vendored
@ -0,0 +1,43 @@ |
||||
/*******************************************************************************
|
||||
* Copyright (c) 2008-2012 The Khronos Group Inc. |
||||
* |
||||
* Permission is hereby granted, free of charge, to any person obtaining a |
||||
* copy of this software and/or associated documentation files (the |
||||
* "Materials"), to deal in the Materials without restriction, including |
||||
* without limitation the rights to use, copy, modify, merge, publish, |
||||
* distribute, sublicense, and/or sell copies of the Materials, and to |
||||
* permit persons to whom the Materials are furnished to do so, subject to |
||||
* the following conditions: |
||||
* |
||||
* The above copyright notice and this permission notice shall be included |
||||
* in all copies or substantial portions of the Materials. |
||||
* |
||||
* THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. |
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY |
||||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, |
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE |
||||
* MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. |
||||
******************************************************************************/ |
||||
|
||||
/* $Revision: 11708 $ on $Date: 2010-06-13 23:36:24 -0700 (Sun, 13 Jun 2010) $ */ |
||||
|
||||
#ifndef __OPENCL_H |
||||
#define __OPENCL_H |
||||
|
||||
#ifdef __cplusplus |
||||
extern "C" { |
||||
#endif |
||||
|
||||
#include <cl.h> |
||||
#include <cl_gl.h> |
||||
#include <cl_gl_ext.h> |
||||
#include <cl_ext.h> |
||||
|
||||
#ifdef __cplusplus |
||||
} |
||||
#endif |
||||
|
||||
#endif /* __OPENCL_H */ |
||||
|
@ -0,0 +1,83 @@ |
||||
// +build cl12
|
||||
|
||||
package cl |
||||
|
||||
// #ifdef __APPLE__
|
||||
// #include "OpenCL/opencl.h"
|
||||
// #else
|
||||
// #include "cl.h"
|
||||
// #endif
|
||||
import "C" |
||||
import ( |
||||
"image" |
||||
"unsafe" |
||||
) |
||||
|
||||
func (ctx *Context) CreateImage(flags MemFlag, imageFormat ImageFormat, imageDesc ImageDescription, data []byte) (*MemObject, error) { |
||||
format := imageFormat.toCl() |
||||
desc := imageDesc.toCl() |
||||
var dataPtr unsafe.Pointer |
||||
if data != nil { |
||||
dataPtr = unsafe.Pointer(&data[0]) |
||||
} |
||||
var err C.cl_int |
||||
clBuffer := C.clCreateImage(ctx.clContext, C.cl_mem_flags(flags), &format, &desc, dataPtr, &err) |
||||
if err != C.CL_SUCCESS { |
||||
return nil, toError(err) |
||||
} |
||||
if clBuffer == nil { |
||||
return nil, ErrUnknown |
||||
} |
||||
return newMemObject(clBuffer, len(data)), nil |
||||
} |
||||
|
||||
func (ctx *Context) CreateImageSimple(flags MemFlag, width, height int, channelOrder ChannelOrder, channelDataType ChannelDataType, data []byte) (*MemObject, error) { |
||||
format := ImageFormat{channelOrder, channelDataType} |
||||
desc := ImageDescription{ |
||||
Type: MemObjectTypeImage2D, |
||||
Width: width, |
||||
Height: height, |
||||
} |
||||
return ctx.CreateImage(flags, format, desc, data) |
||||
} |
||||
|
||||
func (ctx *Context) CreateImageFromImage(flags MemFlag, img image.Image) (*MemObject, error) { |
||||
switch m := img.(type) { |
||||
case *image.Gray: |
||||
format := ImageFormat{ChannelOrderIntensity, ChannelDataTypeUNormInt8} |
||||
desc := ImageDescription{ |
||||
Type: MemObjectTypeImage2D, |
||||
Width: m.Bounds().Dx(), |
||||
Height: m.Bounds().Dy(), |
||||
RowPitch: m.Stride, |
||||
} |
||||
return ctx.CreateImage(flags, format, desc, m.Pix) |
||||
case *image.RGBA: |
||||
format := ImageFormat{ChannelOrderRGBA, ChannelDataTypeUNormInt8} |
||||
desc := ImageDescription{ |
||||
Type: MemObjectTypeImage2D, |
||||
Width: m.Bounds().Dx(), |
||||
Height: m.Bounds().Dy(), |
||||
RowPitch: m.Stride, |
||||
} |
||||
return ctx.CreateImage(flags, format, desc, m.Pix) |
||||
} |
||||
|
||||
b := img.Bounds() |
||||
w := b.Dx() |
||||
h := b.Dy() |
||||
data := make([]byte, w*h*4) |
||||
dataOffset := 0 |
||||
for y := 0; y < h; y++ { |
||||
for x := 0; x < w; x++ { |
||||
c := img.At(x+b.Min.X, y+b.Min.Y) |
||||
r, g, b, a := c.RGBA() |
||||
data[dataOffset] = uint8(r >> 8) |
||||
data[dataOffset+1] = uint8(g >> 8) |
||||
data[dataOffset+2] = uint8(b >> 8) |
||||
data[dataOffset+3] = uint8(a >> 8) |
||||
dataOffset += 4 |
||||
} |
||||
} |
||||
return ctx.CreateImageSimple(flags, w, h, ChannelOrderRGBA, ChannelDataTypeUNormInt8, data) |
||||
} |
@ -0,0 +1,127 @@ |
||||
package cl |
||||
|
||||
// #ifdef __APPLE__
|
||||
// #include "OpenCL/opencl.h"
|
||||
// #else
|
||||
// #include "cl.h"
|
||||
// #endif
|
||||
import "C" |
||||
|
||||
import ( |
||||
"fmt" |
||||
"unsafe" |
||||
) |
||||
|
||||
type ErrUnsupportedArgumentType struct { |
||||
Index int |
||||
Value interface{} |
||||
} |
||||
|
||||
func (e ErrUnsupportedArgumentType) Error() string { |
||||
return fmt.Sprintf("cl: unsupported argument type for index %d: %+v", e.Index, e.Value) |
||||
} |
||||
|
||||
type Kernel struct { |
||||
clKernel C.cl_kernel |
||||
name string |
||||
} |
||||
|
||||
type LocalBuffer int |
||||
|
||||
func releaseKernel(k *Kernel) { |
||||
if k.clKernel != nil { |
||||
C.clReleaseKernel(k.clKernel) |
||||
k.clKernel = nil |
||||
} |
||||
} |
||||
|
||||
func (k *Kernel) Release() { |
||||
releaseKernel(k) |
||||
} |
||||
|
||||
func (k *Kernel) SetArgs(args ...interface{}) error { |
||||
for index, arg := range args { |
||||
if err := k.SetArg(index, arg); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func (k *Kernel) SetArg(index int, arg interface{}) error { |
||||
switch val := arg.(type) { |
||||
case uint8: |
||||
return k.SetArgUint8(index, val) |
||||
case int8: |
||||
return k.SetArgInt8(index, val) |
||||
case uint32: |
||||
return k.SetArgUint32(index, val) |
||||
case uint64: |
||||
return k.SetArgUint64(index, val) |
||||
case int32: |
||||
return k.SetArgInt32(index, val) |
||||
case float32: |
||||
return k.SetArgFloat32(index, val) |
||||
case *MemObject: |
||||
return k.SetArgBuffer(index, val) |
||||
case LocalBuffer: |
||||
return k.SetArgLocal(index, int(val)) |
||||
default: |
||||
return ErrUnsupportedArgumentType{Index: index, Value: arg} |
||||
} |
||||
} |
||||
|
||||
func (k *Kernel) SetArgBuffer(index int, buffer *MemObject) error { |
||||
return k.SetArgUnsafe(index, int(unsafe.Sizeof(buffer.clMem)), unsafe.Pointer(&buffer.clMem)) |
||||
} |
||||
|
||||
func (k *Kernel) SetArgFloat32(index int, val float32) error { |
||||
return k.SetArgUnsafe(index, int(unsafe.Sizeof(val)), unsafe.Pointer(&val)) |
||||
} |
||||
|
||||
func (k *Kernel) SetArgInt8(index int, val int8) error { |
||||
return k.SetArgUnsafe(index, int(unsafe.Sizeof(val)), unsafe.Pointer(&val)) |
||||
} |
||||
|
||||
func (k *Kernel) SetArgUint8(index int, val uint8) error { |
||||
return k.SetArgUnsafe(index, int(unsafe.Sizeof(val)), unsafe.Pointer(&val)) |
||||
} |
||||
|
||||
func (k *Kernel) SetArgInt32(index int, val int32) error { |
||||
return k.SetArgUnsafe(index, int(unsafe.Sizeof(val)), unsafe.Pointer(&val)) |
||||
} |
||||
|
||||
func (k *Kernel) SetArgUint32(index int, val uint32) error { |
||||
return k.SetArgUnsafe(index, int(unsafe.Sizeof(val)), unsafe.Pointer(&val)) |
||||
} |
||||
|
||||
func (k *Kernel) SetArgUint64(index int, val uint64) error { |
||||
return k.SetArgUnsafe(index, int(unsafe.Sizeof(val)), unsafe.Pointer(&val)) |
||||
} |
||||
|
||||
func (k *Kernel) SetArgLocal(index int, size int) error { |
||||
return k.SetArgUnsafe(index, size, nil) |
||||
} |
||||
|
||||
func (k *Kernel) SetArgUnsafe(index, argSize int, arg unsafe.Pointer) error { |
||||
//fmt.Println("FUNKY: ", index, argSize)
|
||||
return toError(C.clSetKernelArg(k.clKernel, C.cl_uint(index), C.size_t(argSize), arg)) |
||||
} |
||||
|
||||
func (k *Kernel) PreferredWorkGroupSizeMultiple(device *Device) (int, error) { |
||||
var size C.size_t |
||||
err := C.clGetKernelWorkGroupInfo(k.clKernel, device.nullableId(), C.CL_KERNEL_PREFERRED_WORK_GROUP_SIZE_MULTIPLE, C.size_t(unsafe.Sizeof(size)), unsafe.Pointer(&size), nil) |
||||
return int(size), toError(err) |
||||
} |
||||
|
||||
func (k *Kernel) WorkGroupSize(device *Device) (int, error) { |
||||
var size C.size_t |
||||
err := C.clGetKernelWorkGroupInfo(k.clKernel, device.nullableId(), C.CL_KERNEL_WORK_GROUP_SIZE, C.size_t(unsafe.Sizeof(size)), unsafe.Pointer(&size), nil) |
||||
return int(size), toError(err) |
||||
} |
||||
|
||||
func (k *Kernel) NumArgs() (int, error) { |
||||
var num C.cl_uint |
||||
err := C.clGetKernelInfo(k.clKernel, C.CL_KERNEL_NUM_ARGS, C.size_t(unsafe.Sizeof(num)), unsafe.Pointer(&num), nil) |
||||
return int(num), toError(err) |
||||
} |
@ -0,0 +1,7 @@ |
||||
// +build !cl12
|
||||
|
||||
package cl |
||||
|
||||
func (k *Kernel) ArgName(index int) (string, error) { |
||||
return "", ErrUnsupported |
||||
} |
@ -0,0 +1,20 @@ |
||||
// +build cl12
|
||||
|
||||
package cl |
||||
|
||||
// #ifdef __APPLE__
|
||||
// #include "OpenCL/opencl.h"
|
||||
// #else
|
||||
// #include "cl.h"
|
||||
// #endif
|
||||
import "C" |
||||
import "unsafe" |
||||
|
||||
func (k *Kernel) ArgName(index int) (string, error) { |
||||
var strC [1024]byte |
||||
var strN C.size_t |
||||
if err := C.clGetKernelArgInfo(k.clKernel, C.cl_uint(index), C.CL_KERNEL_ARG_NAME, 1024, unsafe.Pointer(&strC[0]), &strN); err != C.CL_SUCCESS { |
||||
return "", toError(err) |
||||
} |
||||
return string(strC[:strN]), nil |
||||
} |
@ -0,0 +1,83 @@ |
||||
package cl |
||||
|
||||
// #ifdef __APPLE__
|
||||
// #include "OpenCL/opencl.h"
|
||||
// #else
|
||||
// #include "cl.h"
|
||||
// #endif
|
||||
import "C" |
||||
|
||||
import "unsafe" |
||||
|
||||
const maxPlatforms = 32 |
||||
|
||||
type Platform struct { |
||||
id C.cl_platform_id |
||||
} |
||||
|
||||
// Obtain the list of platforms available.
|
||||
func GetPlatforms() ([]*Platform, error) { |
||||
var platformIds [maxPlatforms]C.cl_platform_id |
||||
var nPlatforms C.cl_uint |
||||
if err := C.clGetPlatformIDs(C.cl_uint(maxPlatforms), &platformIds[0], &nPlatforms); err != C.CL_SUCCESS { |
||||
return nil, toError(err) |
||||
} |
||||
platforms := make([]*Platform, nPlatforms) |
||||
for i := 0; i < int(nPlatforms); i++ { |
||||
platforms[i] = &Platform{id: platformIds[i]} |
||||
} |
||||
return platforms, nil |
||||
} |
||||
|
||||
func (p *Platform) GetDevices(deviceType DeviceType) ([]*Device, error) { |
||||
return GetDevices(p, deviceType) |
||||
} |
||||
|
||||
func (p *Platform) getInfoString(param C.cl_platform_info) (string, error) { |
||||
var strC [2048]byte |
||||
var strN C.size_t |
||||
if err := C.clGetPlatformInfo(p.id, param, 2048, unsafe.Pointer(&strC[0]), &strN); err != C.CL_SUCCESS { |
||||
return "", toError(err) |
||||
} |
||||
return string(strC[:(strN - 1)]), nil |
||||
} |
||||
|
||||
func (p *Platform) Name() string { |
||||
if str, err := p.getInfoString(C.CL_PLATFORM_NAME); err != nil { |
||||
panic("Platform.Name() should never fail") |
||||
} else { |
||||
return str |
||||
} |
||||
} |
||||
|
||||
func (p *Platform) Vendor() string { |
||||
if str, err := p.getInfoString(C.CL_PLATFORM_VENDOR); err != nil { |
||||
panic("Platform.Vendor() should never fail") |
||||
} else { |
||||
return str |
||||
} |
||||
} |
||||
|
||||
func (p *Platform) Profile() string { |
||||
if str, err := p.getInfoString(C.CL_PLATFORM_PROFILE); err != nil { |
||||
panic("Platform.Profile() should never fail") |
||||
} else { |
||||
return str |
||||
} |
||||
} |
||||
|
||||
func (p *Platform) Version() string { |
||||
if str, err := p.getInfoString(C.CL_PLATFORM_VERSION); err != nil { |
||||
panic("Platform.Version() should never fail") |
||||
} else { |
||||
return str |
||||
} |
||||
} |
||||
|
||||
func (p *Platform) Extensions() string { |
||||
if str, err := p.getInfoString(C.CL_PLATFORM_EXTENSIONS); err != nil { |
||||
panic("Platform.Extensions() should never fail") |
||||
} else { |
||||
return str |
||||
} |
||||
} |
@ -0,0 +1,105 @@ |
||||
package cl |
||||
|
||||
// #include <stdlib.h>
|
||||
// #ifdef __APPLE__
|
||||
// #include "OpenCL/opencl.h"
|
||||
// #else
|
||||
// #include "cl.h"
|
||||
// #endif
|
||||
import "C" |
||||
|
||||
import ( |
||||
"fmt" |
||||
"runtime" |
||||
"unsafe" |
||||
) |
||||
|
||||
type BuildError struct { |
||||
Message string |
||||
Device *Device |
||||
} |
||||
|
||||
func (e BuildError) Error() string { |
||||
if e.Device != nil { |
||||
return fmt.Sprintf("cl: build error on %q: %s", e.Device.Name(), e.Message) |
||||
} else { |
||||
return fmt.Sprintf("cl: build error: %s", e.Message) |
||||
} |
||||
} |
||||
|
||||
type Program struct { |
||||
clProgram C.cl_program |
||||
devices []*Device |
||||
} |
||||
|
||||
func releaseProgram(p *Program) { |
||||
if p.clProgram != nil { |
||||
C.clReleaseProgram(p.clProgram) |
||||
p.clProgram = nil |
||||
} |
||||
} |
||||
|
||||
func (p *Program) Release() { |
||||
releaseProgram(p) |
||||
} |
||||
|
||||
func (p *Program) BuildProgram(devices []*Device, options string) error { |
||||
var cOptions *C.char |
||||
if options != "" { |
||||
cOptions = C.CString(options) |
||||
defer C.free(unsafe.Pointer(cOptions)) |
||||
} |
||||
var deviceList []C.cl_device_id |
||||
var deviceListPtr *C.cl_device_id |
||||
numDevices := C.cl_uint(len(devices)) |
||||
if devices != nil && len(devices) > 0 { |
||||
deviceList = buildDeviceIdList(devices) |
||||
deviceListPtr = &deviceList[0] |
||||
} |
||||
if err := C.clBuildProgram(p.clProgram, numDevices, deviceListPtr, cOptions, nil, nil); err != C.CL_SUCCESS { |
||||
buffer := make([]byte, 4096) |
||||
var bLen C.size_t |
||||
var err C.cl_int |
||||
|
||||
for _, dev := range p.devices { |
||||
for i := 2; i >= 0; i-- { |
||||
err = C.clGetProgramBuildInfo(p.clProgram, dev.id, C.CL_PROGRAM_BUILD_LOG, C.size_t(len(buffer)), unsafe.Pointer(&buffer[0]), &bLen) |
||||
if err == C.CL_INVALID_VALUE && i > 0 && bLen < 1024*1024 { |
||||
// INVALID_VALUE probably means our buffer isn't large enough
|
||||
buffer = make([]byte, bLen) |
||||
} else { |
||||
break |
||||
} |
||||
} |
||||
if err != C.CL_SUCCESS { |
||||
return toError(err) |
||||
} |
||||
|
||||
if bLen > 1 { |
||||
return BuildError{ |
||||
Device: dev, |
||||
Message: string(buffer[:bLen-1]), |
||||
} |
||||
} |
||||
} |
||||
|
||||
return BuildError{ |
||||
Device: nil, |
||||
Message: "build failed and produced no log entries", |
||||
} |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func (p *Program) CreateKernel(name string) (*Kernel, error) { |
||||
cName := C.CString(name) |
||||
defer C.free(unsafe.Pointer(cName)) |
||||
var err C.cl_int |
||||
clKernel := C.clCreateKernel(p.clProgram, cName, &err) |
||||
if err != C.CL_SUCCESS { |
||||
return nil, toError(err) |
||||
} |
||||
kernel := &Kernel{clKernel: clKernel, name: name} |
||||
runtime.SetFinalizer(kernel, releaseKernel) |
||||
return kernel, nil |
||||
} |
@ -0,0 +1,193 @@ |
||||
package cl |
||||
|
||||
// #ifdef __APPLE__
|
||||
// #include "OpenCL/opencl.h"
|
||||
// #else
|
||||
// #include "cl.h"
|
||||
// #endif
|
||||
import "C" |
||||
|
||||
import "unsafe" |
||||
|
||||
type CommandQueueProperty int |
||||
|
||||
const ( |
||||
CommandQueueOutOfOrderExecModeEnable CommandQueueProperty = C.CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE |
||||
CommandQueueProfilingEnable CommandQueueProperty = C.CL_QUEUE_PROFILING_ENABLE |
||||
) |
||||
|
||||
type CommandQueue struct { |
||||
clQueue C.cl_command_queue |
||||
device *Device |
||||
} |
||||
|
||||
func releaseCommandQueue(q *CommandQueue) { |
||||
if q.clQueue != nil { |
||||
C.clReleaseCommandQueue(q.clQueue) |
||||
q.clQueue = nil |
||||
} |
||||
} |
||||
|
||||
// Call clReleaseCommandQueue on the CommandQueue. Using the CommandQueue after Release will cause a panick.
|
||||
func (q *CommandQueue) Release() { |
||||
releaseCommandQueue(q) |
||||
} |
||||
|
||||
// Blocks until all previously queued OpenCL commands in a command-queue are issued to the associated device and have completed.
|
||||
func (q *CommandQueue) Finish() error { |
||||
return toError(C.clFinish(q.clQueue)) |
||||
} |
||||
|
||||
// Issues all previously queued OpenCL commands in a command-queue to the device associated with the command-queue.
|
||||
func (q *CommandQueue) Flush() error { |
||||
return toError(C.clFlush(q.clQueue)) |
||||
} |
||||
|
||||
// Enqueues a command to map a region of the buffer object given by buffer into the host address space and returns a pointer to this mapped region.
|
||||
func (q *CommandQueue) EnqueueMapBuffer(buffer *MemObject, blocking bool, flags MapFlag, offset, size int, eventWaitList []*Event) (*MappedMemObject, *Event, error) { |
||||
var event C.cl_event |
||||
var err C.cl_int |
||||
ptr := C.clEnqueueMapBuffer(q.clQueue, buffer.clMem, clBool(blocking), flags.toCl(), C.size_t(offset), C.size_t(size), C.cl_uint(len(eventWaitList)), eventListPtr(eventWaitList), &event, &err) |
||||
if err != C.CL_SUCCESS { |
||||
return nil, nil, toError(err) |
||||
} |
||||
ev := newEvent(event) |
||||
if ptr == nil { |
||||
return nil, ev, ErrUnknown |
||||
} |
||||
return &MappedMemObject{ptr: ptr, size: size}, ev, nil |
||||
} |
||||
|
||||
// Enqueues a command to map a region of an image object into the host address space and returns a pointer to this mapped region.
|
||||
func (q *CommandQueue) EnqueueMapImage(buffer *MemObject, blocking bool, flags MapFlag, origin, region [3]int, eventWaitList []*Event) (*MappedMemObject, *Event, error) { |
||||
cOrigin := sizeT3(origin) |
||||
cRegion := sizeT3(region) |
||||
var event C.cl_event |
||||
var err C.cl_int |
||||
var rowPitch, slicePitch C.size_t |
||||
ptr := C.clEnqueueMapImage(q.clQueue, buffer.clMem, clBool(blocking), flags.toCl(), &cOrigin[0], &cRegion[0], &rowPitch, &slicePitch, C.cl_uint(len(eventWaitList)), eventListPtr(eventWaitList), &event, &err) |
||||
if err != C.CL_SUCCESS { |
||||
return nil, nil, toError(err) |
||||
} |
||||
ev := newEvent(event) |
||||
if ptr == nil { |
||||
return nil, ev, ErrUnknown |
||||
} |
||||
size := 0 // TODO: could calculate this
|
||||
return &MappedMemObject{ptr: ptr, size: size, rowPitch: int(rowPitch), slicePitch: int(slicePitch)}, ev, nil |
||||
} |
||||
|
||||
// Enqueues a command to unmap a previously mapped region of a memory object.
|
||||
func (q *CommandQueue) EnqueueUnmapMemObject(buffer *MemObject, mappedObj *MappedMemObject, eventWaitList []*Event) (*Event, error) { |
||||
var event C.cl_event |
||||
if err := C.clEnqueueUnmapMemObject(q.clQueue, buffer.clMem, mappedObj.ptr, C.cl_uint(len(eventWaitList)), eventListPtr(eventWaitList), &event); err != C.CL_SUCCESS { |
||||
return nil, toError(err) |
||||
} |
||||
return newEvent(event), nil |
||||
} |
||||
|
||||
// Enqueues a command to copy a buffer object to another buffer object.
|
||||
func (q *CommandQueue) EnqueueCopyBuffer(srcBuffer, dstBuffer *MemObject, srcOffset, dstOffset, byteCount int, eventWaitList []*Event) (*Event, error) { |
||||
var event C.cl_event |
||||
err := toError(C.clEnqueueCopyBuffer(q.clQueue, srcBuffer.clMem, dstBuffer.clMem, C.size_t(srcOffset), C.size_t(dstOffset), C.size_t(byteCount), C.cl_uint(len(eventWaitList)), eventListPtr(eventWaitList), &event)) |
||||
return newEvent(event), err |
||||
} |
||||
|
||||
// Enqueue commands to write to a buffer object from host memory.
|
||||
func (q *CommandQueue) EnqueueWriteBuffer(buffer *MemObject, blocking bool, offset, dataSize int, dataPtr unsafe.Pointer, eventWaitList []*Event) (*Event, error) { |
||||
var event C.cl_event |
||||
err := toError(C.clEnqueueWriteBuffer(q.clQueue, buffer.clMem, clBool(blocking), C.size_t(offset), C.size_t(dataSize), dataPtr, C.cl_uint(len(eventWaitList)), eventListPtr(eventWaitList), &event)) |
||||
return newEvent(event), err |
||||
} |
||||
|
||||
func (q *CommandQueue) EnqueueWriteBufferFloat32(buffer *MemObject, blocking bool, offset int, data []float32, eventWaitList []*Event) (*Event, error) { |
||||
dataPtr := unsafe.Pointer(&data[0]) |
||||
dataSize := int(unsafe.Sizeof(data[0])) * len(data) |
||||
return q.EnqueueWriteBuffer(buffer, blocking, offset, dataSize, dataPtr, eventWaitList) |
||||
} |
||||
|
||||
// Enqueue commands to read from a buffer object to host memory.
|
||||
func (q *CommandQueue) EnqueueReadBuffer(buffer *MemObject, blocking bool, offset, dataSize int, dataPtr unsafe.Pointer, eventWaitList []*Event) (*Event, error) { |
||||
var event C.cl_event |
||||
err := toError(C.clEnqueueReadBuffer(q.clQueue, buffer.clMem, clBool(blocking), C.size_t(offset), C.size_t(dataSize), dataPtr, C.cl_uint(len(eventWaitList)), eventListPtr(eventWaitList), &event)) |
||||
return newEvent(event), err |
||||
} |
||||
|
||||
func (q *CommandQueue) EnqueueReadBufferFloat32(buffer *MemObject, blocking bool, offset int, data []float32, eventWaitList []*Event) (*Event, error) { |
||||
dataPtr := unsafe.Pointer(&data[0]) |
||||
dataSize := int(unsafe.Sizeof(data[0])) * len(data) |
||||
return q.EnqueueReadBuffer(buffer, blocking, offset, dataSize, dataPtr, eventWaitList) |
||||
} |
||||
|
||||
// Enqueues a command to execute a kernel on a device.
|
||||
func (q *CommandQueue) EnqueueNDRangeKernel(kernel *Kernel, globalWorkOffset, globalWorkSize, localWorkSize []int, eventWaitList []*Event) (*Event, error) { |
||||
workDim := len(globalWorkSize) |
||||
var globalWorkOffsetList []C.size_t |
||||
var globalWorkOffsetPtr *C.size_t |
||||
if globalWorkOffset != nil { |
||||
globalWorkOffsetList = make([]C.size_t, len(globalWorkOffset)) |
||||
for i, off := range globalWorkOffset { |
||||
globalWorkOffsetList[i] = C.size_t(off) |
||||
} |
||||
globalWorkOffsetPtr = &globalWorkOffsetList[0] |
||||
} |
||||
var globalWorkSizeList []C.size_t |
||||
var globalWorkSizePtr *C.size_t |
||||
if globalWorkSize != nil { |
||||
globalWorkSizeList = make([]C.size_t, len(globalWorkSize)) |
||||
for i, off := range globalWorkSize { |
||||
globalWorkSizeList[i] = C.size_t(off) |
||||
} |
||||
globalWorkSizePtr = &globalWorkSizeList[0] |
||||
} |
||||
var localWorkSizeList []C.size_t |
||||
var localWorkSizePtr *C.size_t |
||||
if localWorkSize != nil { |
||||
localWorkSizeList = make([]C.size_t, len(localWorkSize)) |
||||
for i, off := range localWorkSize { |
||||
localWorkSizeList[i] = C.size_t(off) |
||||
} |
||||
localWorkSizePtr = &localWorkSizeList[0] |
||||
} |
||||
var event C.cl_event |
||||
err := toError(C.clEnqueueNDRangeKernel(q.clQueue, kernel.clKernel, C.cl_uint(workDim), globalWorkOffsetPtr, globalWorkSizePtr, localWorkSizePtr, C.cl_uint(len(eventWaitList)), eventListPtr(eventWaitList), &event)) |
||||
return newEvent(event), err |
||||
} |
||||
|
||||
// Enqueues a command to read from a 2D or 3D image object to host memory.
|
||||
func (q *CommandQueue) EnqueueReadImage(image *MemObject, blocking bool, origin, region [3]int, rowPitch, slicePitch int, data []byte, eventWaitList []*Event) (*Event, error) { |
||||
cOrigin := sizeT3(origin) |
||||
cRegion := sizeT3(region) |
||||
var event C.cl_event |
||||
err := toError(C.clEnqueueReadImage(q.clQueue, image.clMem, clBool(blocking), &cOrigin[0], &cRegion[0], C.size_t(rowPitch), C.size_t(slicePitch), unsafe.Pointer(&data[0]), C.cl_uint(len(eventWaitList)), eventListPtr(eventWaitList), &event)) |
||||
return newEvent(event), err |
||||
} |
||||
|
||||
// Enqueues a command to write from a 2D or 3D image object to host memory.
|
||||
func (q *CommandQueue) EnqueueWriteImage(image *MemObject, blocking bool, origin, region [3]int, rowPitch, slicePitch int, data []byte, eventWaitList []*Event) (*Event, error) { |
||||
cOrigin := sizeT3(origin) |
||||
cRegion := sizeT3(region) |
||||
var event C.cl_event |
||||
err := toError(C.clEnqueueWriteImage(q.clQueue, image.clMem, clBool(blocking), &cOrigin[0], &cRegion[0], C.size_t(rowPitch), C.size_t(slicePitch), unsafe.Pointer(&data[0]), C.cl_uint(len(eventWaitList)), eventListPtr(eventWaitList), &event)) |
||||
return newEvent(event), err |
||||
} |
||||
|
||||
func (q *CommandQueue) EnqueueFillBuffer(buffer *MemObject, pattern unsafe.Pointer, patternSize, offset, size int, eventWaitList []*Event) (*Event, error) { |
||||
var event C.cl_event |
||||
err := toError(C.clEnqueueFillBuffer(q.clQueue, buffer.clMem, pattern, C.size_t(patternSize), C.size_t(offset), C.size_t(size), C.cl_uint(len(eventWaitList)), eventListPtr(eventWaitList), &event)) |
||||
return newEvent(event), err |
||||
} |
||||
|
||||
// A synchronization point that enqueues a barrier operation.
|
||||
func (q *CommandQueue) EnqueueBarrierWithWaitList(eventWaitList []*Event) (*Event, error) { |
||||
var event C.cl_event |
||||
err := toError(C.clEnqueueBarrierWithWaitList(q.clQueue, C.cl_uint(len(eventWaitList)), eventListPtr(eventWaitList), &event)) |
||||
return newEvent(event), err |
||||
} |
||||
|
||||
// Enqueues a marker command which waits for either a list of events to complete, or all previously enqueued commands to complete.
|
||||
func (q *CommandQueue) EnqueueMarkerWithWaitList(eventWaitList []*Event) (*Event, error) { |
||||
var event C.cl_event |
||||
err := toError(C.clEnqueueMarkerWithWaitList(q.clQueue, C.cl_uint(len(eventWaitList)), eventListPtr(eventWaitList), &event)) |
||||
return newEvent(event), err |
||||
} |
@ -0,0 +1,487 @@ |
||||
package cl |
||||
|
||||
// #ifdef __APPLE__
|
||||
// #include "OpenCL/opencl.h"
|
||||
// #else
|
||||
// #include "cl.h"
|
||||
// #endif
|
||||
import "C" |
||||
|
||||
import ( |
||||
"errors" |
||||
"fmt" |
||||
"reflect" |
||||
"runtime" |
||||
"strings" |
||||
"unsafe" |
||||
) |
||||
|
||||
var ( |
||||
ErrUnknown = errors.New("cl: unknown error") // Generally an unexpected result from an OpenCL function (e.g. CL_SUCCESS but null pointer)
|
||||
) |
||||
|
||||
type ErrOther int |
||||
|
||||
func (e ErrOther) Error() string { |
||||
return fmt.Sprintf("cl: error %d", int(e)) |
||||
} |
||||
|
||||
var ( |
||||
ErrDeviceNotFound = errors.New("cl: Device Not Found") |
||||
ErrDeviceNotAvailable = errors.New("cl: Device Not Available") |
||||
ErrCompilerNotAvailable = errors.New("cl: Compiler Not Available") |
||||
ErrMemObjectAllocationFailure = errors.New("cl: Mem Object Allocation Failure") |
||||
ErrOutOfResources = errors.New("cl: Out Of Resources") |
||||
ErrOutOfHostMemory = errors.New("cl: Out Of Host Memory") |
||||
ErrProfilingInfoNotAvailable = errors.New("cl: Profiling Info Not Available") |
||||
ErrMemCopyOverlap = errors.New("cl: Mem Copy Overlap") |
||||
ErrImageFormatMismatch = errors.New("cl: Image Format Mismatch") |
||||
ErrImageFormatNotSupported = errors.New("cl: Image Format Not Supported") |
||||
ErrBuildProgramFailure = errors.New("cl: Build Program Failure") |
||||
ErrMapFailure = errors.New("cl: Map Failure") |
||||
ErrMisalignedSubBufferOffset = errors.New("cl: Misaligned Sub Buffer Offset") |
||||
ErrExecStatusErrorForEventsInWaitList = errors.New("cl: Exec Status Error For Events In Wait List") |
||||
ErrCompileProgramFailure = errors.New("cl: Compile Program Failure") |
||||
ErrLinkerNotAvailable = errors.New("cl: Linker Not Available") |
||||
ErrLinkProgramFailure = errors.New("cl: Link Program Failure") |
||||
ErrDevicePartitionFailed = errors.New("cl: Device Partition Failed") |
||||
ErrKernelArgInfoNotAvailable = errors.New("cl: Kernel Arg Info Not Available") |
||||
ErrInvalidValue = errors.New("cl: Invalid Value") |
||||
ErrInvalidDeviceType = errors.New("cl: Invalid Device Type") |
||||
ErrInvalidPlatform = errors.New("cl: Invalid Platform") |
||||
ErrInvalidDevice = errors.New("cl: Invalid Device") |
||||
ErrInvalidContext = errors.New("cl: Invalid Context") |
||||
ErrInvalidQueueProperties = errors.New("cl: Invalid Queue Properties") |
||||
ErrInvalidCommandQueue = errors.New("cl: Invalid Command Queue") |
||||
ErrInvalidHostPtr = errors.New("cl: Invalid Host Ptr") |
||||
ErrInvalidMemObject = errors.New("cl: Invalid Mem Object") |
||||
ErrInvalidImageFormatDescriptor = errors.New("cl: Invalid Image Format Descriptor") |
||||
ErrInvalidImageSize = errors.New("cl: Invalid Image Size") |
||||
ErrInvalidSampler = errors.New("cl: Invalid Sampler") |
||||
ErrInvalidBinary = errors.New("cl: Invalid Binary") |
||||
ErrInvalidBuildOptions = errors.New("cl: Invalid Build Options") |
||||
ErrInvalidProgram = errors.New("cl: Invalid Program") |
||||
ErrInvalidProgramExecutable = errors.New("cl: Invalid Program Executable") |
||||
ErrInvalidKernelName = errors.New("cl: Invalid Kernel Name") |
||||
ErrInvalidKernelDefinition = errors.New("cl: Invalid Kernel Definition") |
||||
ErrInvalidKernel = errors.New("cl: Invalid Kernel") |
||||
ErrInvalidArgIndex = errors.New("cl: Invalid Arg Index") |
||||
ErrInvalidArgValue = errors.New("cl: Invalid Arg Value") |
||||
ErrInvalidArgSize = errors.New("cl: Invalid Arg Size") |
||||
ErrInvalidKernelArgs = errors.New("cl: Invalid Kernel Args") |
||||
ErrInvalidWorkDimension = errors.New("cl: Invalid Work Dimension") |
||||
ErrInvalidWorkGroupSize = errors.New("cl: Invalid Work Group Size") |
||||
ErrInvalidWorkItemSize = errors.New("cl: Invalid Work Item Size") |
||||
ErrInvalidGlobalOffset = errors.New("cl: Invalid Global Offset") |
||||
ErrInvalidEventWaitList = errors.New("cl: Invalid Event Wait List") |
||||
ErrInvalidEvent = errors.New("cl: Invalid Event") |
||||
ErrInvalidOperation = errors.New("cl: Invalid Operation") |
||||
ErrInvalidGlObject = errors.New("cl: Invalid Gl Object") |
||||
ErrInvalidBufferSize = errors.New("cl: Invalid Buffer Size") |
||||
ErrInvalidMipLevel = errors.New("cl: Invalid Mip Level") |
||||
ErrInvalidGlobalWorkSize = errors.New("cl: Invalid Global Work Size") |
||||
ErrInvalidProperty = errors.New("cl: Invalid Property") |
||||
ErrInvalidImageDescriptor = errors.New("cl: Invalid Image Descriptor") |
||||
ErrInvalidCompilerOptions = errors.New("cl: Invalid Compiler Options") |
||||
ErrInvalidLinkerOptions = errors.New("cl: Invalid Linker Options") |
||||
ErrInvalidDevicePartitionCount = errors.New("cl: Invalid Device Partition Count") |
||||
) |
||||
var errorMap = map[C.cl_int]error{ |
||||
C.CL_SUCCESS: nil, |
||||
C.CL_DEVICE_NOT_FOUND: ErrDeviceNotFound, |
||||
C.CL_DEVICE_NOT_AVAILABLE: ErrDeviceNotAvailable, |
||||
C.CL_COMPILER_NOT_AVAILABLE: ErrCompilerNotAvailable, |
||||
C.CL_MEM_OBJECT_ALLOCATION_FAILURE: ErrMemObjectAllocationFailure, |
||||
C.CL_OUT_OF_RESOURCES: ErrOutOfResources, |
||||
C.CL_OUT_OF_HOST_MEMORY: ErrOutOfHostMemory, |
||||
C.CL_PROFILING_INFO_NOT_AVAILABLE: ErrProfilingInfoNotAvailable, |
||||
C.CL_MEM_COPY_OVERLAP: ErrMemCopyOverlap, |
||||
C.CL_IMAGE_FORMAT_MISMATCH: ErrImageFormatMismatch, |
||||
C.CL_IMAGE_FORMAT_NOT_SUPPORTED: ErrImageFormatNotSupported, |
||||
C.CL_BUILD_PROGRAM_FAILURE: ErrBuildProgramFailure, |
||||
C.CL_MAP_FAILURE: ErrMapFailure, |
||||
C.CL_MISALIGNED_SUB_BUFFER_OFFSET: ErrMisalignedSubBufferOffset, |
||||
C.CL_EXEC_STATUS_ERROR_FOR_EVENTS_IN_WAIT_LIST: ErrExecStatusErrorForEventsInWaitList, |
||||
C.CL_INVALID_VALUE: ErrInvalidValue, |
||||
C.CL_INVALID_DEVICE_TYPE: ErrInvalidDeviceType, |
||||
C.CL_INVALID_PLATFORM: ErrInvalidPlatform, |
||||
C.CL_INVALID_DEVICE: ErrInvalidDevice, |
||||
C.CL_INVALID_CONTEXT: ErrInvalidContext, |
||||
C.CL_INVALID_QUEUE_PROPERTIES: ErrInvalidQueueProperties, |
||||
C.CL_INVALID_COMMAND_QUEUE: ErrInvalidCommandQueue, |
||||
C.CL_INVALID_HOST_PTR: ErrInvalidHostPtr, |
||||
C.CL_INVALID_MEM_OBJECT: ErrInvalidMemObject, |
||||
C.CL_INVALID_IMAGE_FORMAT_DESCRIPTOR: ErrInvalidImageFormatDescriptor, |
||||
C.CL_INVALID_IMAGE_SIZE: ErrInvalidImageSize, |
||||
C.CL_INVALID_SAMPLER: ErrInvalidSampler, |
||||
C.CL_INVALID_BINARY: ErrInvalidBinary, |
||||
C.CL_INVALID_BUILD_OPTIONS: ErrInvalidBuildOptions, |
||||
C.CL_INVALID_PROGRAM: ErrInvalidProgram, |
||||
C.CL_INVALID_PROGRAM_EXECUTABLE: ErrInvalidProgramExecutable, |
||||
C.CL_INVALID_KERNEL_NAME: ErrInvalidKernelName, |
||||
C.CL_INVALID_KERNEL_DEFINITION: ErrInvalidKernelDefinition, |
||||
C.CL_INVALID_KERNEL: ErrInvalidKernel, |
||||
C.CL_INVALID_ARG_INDEX: ErrInvalidArgIndex, |
||||
C.CL_INVALID_ARG_VALUE: ErrInvalidArgValue, |
||||
C.CL_INVALID_ARG_SIZE: ErrInvalidArgSize, |
||||
C.CL_INVALID_KERNEL_ARGS: ErrInvalidKernelArgs, |
||||
C.CL_INVALID_WORK_DIMENSION: ErrInvalidWorkDimension, |
||||
C.CL_INVALID_WORK_GROUP_SIZE: ErrInvalidWorkGroupSize, |
||||
C.CL_INVALID_WORK_ITEM_SIZE: ErrInvalidWorkItemSize, |
||||
C.CL_INVALID_GLOBAL_OFFSET: ErrInvalidGlobalOffset, |
||||
C.CL_INVALID_EVENT_WAIT_LIST: ErrInvalidEventWaitList, |
||||
C.CL_INVALID_EVENT: ErrInvalidEvent, |
||||
C.CL_INVALID_OPERATION: ErrInvalidOperation, |
||||
C.CL_INVALID_GL_OBJECT: ErrInvalidGlObject, |
||||
C.CL_INVALID_BUFFER_SIZE: ErrInvalidBufferSize, |
||||
C.CL_INVALID_MIP_LEVEL: ErrInvalidMipLevel, |
||||
C.CL_INVALID_GLOBAL_WORK_SIZE: ErrInvalidGlobalWorkSize, |
||||
C.CL_INVALID_PROPERTY: ErrInvalidProperty, |
||||
} |
||||
|
||||
func toError(code C.cl_int) error { |
||||
if err, ok := errorMap[code]; ok { |
||||
return err |
||||
} |
||||
return ErrOther(code) |
||||
} |
||||
|
||||
type LocalMemType int |
||||
|
||||
const ( |
||||
LocalMemTypeNone LocalMemType = C.CL_NONE |
||||
LocalMemTypeGlobal LocalMemType = C.CL_GLOBAL |
||||
LocalMemTypeLocal LocalMemType = C.CL_LOCAL |
||||
) |
||||
|
||||
var localMemTypeMap = map[LocalMemType]string{ |
||||
LocalMemTypeNone: "None", |
||||
LocalMemTypeGlobal: "Global", |
||||
LocalMemTypeLocal: "Local", |
||||
} |
||||
|
||||
func (t LocalMemType) String() string { |
||||
name := localMemTypeMap[t] |
||||
if name == "" { |
||||
name = "Unknown" |
||||
} |
||||
return name |
||||
} |
||||
|
||||
type ExecCapability int |
||||
|
||||
const ( |
||||
ExecCapabilityKernel ExecCapability = C.CL_EXEC_KERNEL // The OpenCL device can execute OpenCL kernels.
|
||||
ExecCapabilityNativeKernel ExecCapability = C.CL_EXEC_NATIVE_KERNEL // The OpenCL device can execute native kernels.
|
||||
) |
||||
|
||||
func (ec ExecCapability) String() string { |
||||
var parts []string |
||||
if ec&ExecCapabilityKernel != 0 { |
||||
parts = append(parts, "Kernel") |
||||
} |
||||
if ec&ExecCapabilityNativeKernel != 0 { |
||||
parts = append(parts, "NativeKernel") |
||||
} |
||||
if parts == nil { |
||||
return "" |
||||
} |
||||
return strings.Join(parts, "|") |
||||
} |
||||
|
||||
type MemCacheType int |
||||
|
||||
const ( |
||||
MemCacheTypeNone MemCacheType = C.CL_NONE |
||||
MemCacheTypeReadOnlyCache MemCacheType = C.CL_READ_ONLY_CACHE |
||||
MemCacheTypeReadWriteCache MemCacheType = C.CL_READ_WRITE_CACHE |
||||
) |
||||
|
||||
func (ct MemCacheType) String() string { |
||||
switch ct { |
||||
case MemCacheTypeNone: |
||||
return "None" |
||||
case MemCacheTypeReadOnlyCache: |
||||
return "ReadOnly" |
||||
case MemCacheTypeReadWriteCache: |
||||
return "ReadWrite" |
||||
} |
||||
return fmt.Sprintf("Unknown(%x)", int(ct)) |
||||
} |
||||
|
||||
type MemFlag int |
||||
|
||||
const ( |
||||
MemReadWrite MemFlag = C.CL_MEM_READ_WRITE |
||||
MemWriteOnly MemFlag = C.CL_MEM_WRITE_ONLY |
||||
MemReadOnly MemFlag = C.CL_MEM_READ_ONLY |
||||
MemUseHostPtr MemFlag = C.CL_MEM_USE_HOST_PTR |
||||
MemAllocHostPtr MemFlag = C.CL_MEM_ALLOC_HOST_PTR |
||||
MemCopyHostPtr MemFlag = C.CL_MEM_COPY_HOST_PTR |
||||
|
||||
MemWriteOnlyHost MemFlag = C.CL_MEM_HOST_WRITE_ONLY |
||||
MemReadOnlyHost MemFlag = C.CL_MEM_HOST_READ_ONLY |
||||
MemNoAccessHost MemFlag = C.CL_MEM_HOST_NO_ACCESS |
||||
) |
||||
|
||||
type MemObjectType int |
||||
|
||||
const ( |
||||
MemObjectTypeBuffer MemObjectType = C.CL_MEM_OBJECT_BUFFER |
||||
MemObjectTypeImage2D MemObjectType = C.CL_MEM_OBJECT_IMAGE2D |
||||
MemObjectTypeImage3D MemObjectType = C.CL_MEM_OBJECT_IMAGE3D |
||||
) |
||||
|
||||
type MapFlag int |
||||
|
||||
const ( |
||||
// This flag specifies that the region being mapped in the memory object is being mapped for reading.
|
||||
MapFlagRead MapFlag = C.CL_MAP_READ |
||||
MapFlagWrite MapFlag = C.CL_MAP_WRITE |
||||
MapFlagWriteInvalidateRegion MapFlag = C.CL_MAP_WRITE_INVALIDATE_REGION |
||||
) |
||||
|
||||
func (mf MapFlag) toCl() C.cl_map_flags { |
||||
return C.cl_map_flags(mf) |
||||
} |
||||
|
||||
type ChannelOrder int |
||||
|
||||
const ( |
||||
ChannelOrderR ChannelOrder = C.CL_R |
||||
ChannelOrderA ChannelOrder = C.CL_A |
||||
ChannelOrderRG ChannelOrder = C.CL_RG |
||||
ChannelOrderRA ChannelOrder = C.CL_RA |
||||
ChannelOrderRGB ChannelOrder = C.CL_RGB |
||||
ChannelOrderRGBA ChannelOrder = C.CL_RGBA |
||||
ChannelOrderBGRA ChannelOrder = C.CL_BGRA |
||||
ChannelOrderARGB ChannelOrder = C.CL_ARGB |
||||
ChannelOrderIntensity ChannelOrder = C.CL_INTENSITY |
||||
ChannelOrderLuminance ChannelOrder = C.CL_LUMINANCE |
||||
ChannelOrderRx ChannelOrder = C.CL_Rx |
||||
ChannelOrderRGx ChannelOrder = C.CL_RGx |
||||
ChannelOrderRGBx ChannelOrder = C.CL_RGBx |
||||
) |
||||
|
||||
var channelOrderNameMap = map[ChannelOrder]string{ |
||||
ChannelOrderR: "R", |
||||
ChannelOrderA: "A", |
||||
ChannelOrderRG: "RG", |
||||
ChannelOrderRA: "RA", |
||||
ChannelOrderRGB: "RGB", |
||||
ChannelOrderRGBA: "RGBA", |
||||
ChannelOrderBGRA: "BGRA", |
||||
ChannelOrderARGB: "ARGB", |
||||
ChannelOrderIntensity: "Intensity", |
||||
ChannelOrderLuminance: "Luminance", |
||||
ChannelOrderRx: "Rx", |
||||
ChannelOrderRGx: "RGx", |
||||
ChannelOrderRGBx: "RGBx", |
||||
} |
||||
|
||||
func (co ChannelOrder) String() string { |
||||
name := channelOrderNameMap[co] |
||||
if name == "" { |
||||
name = fmt.Sprintf("Unknown(%x)", int(co)) |
||||
} |
||||
return name |
||||
} |
||||
|
||||
type ChannelDataType int |
||||
|
||||
const ( |
||||
ChannelDataTypeSNormInt8 ChannelDataType = C.CL_SNORM_INT8 |
||||
ChannelDataTypeSNormInt16 ChannelDataType = C.CL_SNORM_INT16 |
||||
ChannelDataTypeUNormInt8 ChannelDataType = C.CL_UNORM_INT8 |
||||
ChannelDataTypeUNormInt16 ChannelDataType = C.CL_UNORM_INT16 |
||||
ChannelDataTypeUNormShort565 ChannelDataType = C.CL_UNORM_SHORT_565 |
||||
ChannelDataTypeUNormShort555 ChannelDataType = C.CL_UNORM_SHORT_555 |
||||
ChannelDataTypeUNormInt101010 ChannelDataType = C.CL_UNORM_INT_101010 |
||||
ChannelDataTypeSignedInt8 ChannelDataType = C.CL_SIGNED_INT8 |
||||
ChannelDataTypeSignedInt16 ChannelDataType = C.CL_SIGNED_INT16 |
||||
ChannelDataTypeSignedInt32 ChannelDataType = C.CL_SIGNED_INT32 |
||||
ChannelDataTypeUnsignedInt8 ChannelDataType = C.CL_UNSIGNED_INT8 |
||||
ChannelDataTypeUnsignedInt16 ChannelDataType = C.CL_UNSIGNED_INT16 |
||||
ChannelDataTypeUnsignedInt32 ChannelDataType = C.CL_UNSIGNED_INT32 |
||||
ChannelDataTypeHalfFloat ChannelDataType = C.CL_HALF_FLOAT |
||||
ChannelDataTypeFloat ChannelDataType = C.CL_FLOAT |
||||
) |
||||
|
||||
var channelDataTypeNameMap = map[ChannelDataType]string{ |
||||
ChannelDataTypeSNormInt8: "SNormInt8", |
||||
ChannelDataTypeSNormInt16: "SNormInt16", |
||||
ChannelDataTypeUNormInt8: "UNormInt8", |
||||
ChannelDataTypeUNormInt16: "UNormInt16", |
||||
ChannelDataTypeUNormShort565: "UNormShort565", |
||||
ChannelDataTypeUNormShort555: "UNormShort555", |
||||
ChannelDataTypeUNormInt101010: "UNormInt101010", |
||||
ChannelDataTypeSignedInt8: "SignedInt8", |
||||
ChannelDataTypeSignedInt16: "SignedInt16", |
||||
ChannelDataTypeSignedInt32: "SignedInt32", |
||||
ChannelDataTypeUnsignedInt8: "UnsignedInt8", |
||||
ChannelDataTypeUnsignedInt16: "UnsignedInt16", |
||||
ChannelDataTypeUnsignedInt32: "UnsignedInt32", |
||||
ChannelDataTypeHalfFloat: "HalfFloat", |
||||
ChannelDataTypeFloat: "Float", |
||||
} |
||||
|
||||
func (ct ChannelDataType) String() string { |
||||
name := channelDataTypeNameMap[ct] |
||||
if name == "" { |
||||
name = fmt.Sprintf("Unknown(%x)", int(ct)) |
||||
} |
||||
return name |
||||
} |
||||
|
||||
type ImageFormat struct { |
||||
ChannelOrder ChannelOrder |
||||
ChannelDataType ChannelDataType |
||||
} |
||||
|
||||
func (f ImageFormat) toCl() C.cl_image_format { |
||||
var format C.cl_image_format |
||||
format.image_channel_order = C.cl_channel_order(f.ChannelOrder) |
||||
format.image_channel_data_type = C.cl_channel_type(f.ChannelDataType) |
||||
return format |
||||
} |
||||
|
||||
type ProfilingInfo int |
||||
|
||||
const ( |
||||
// A 64-bit value that describes the current device time counter in
|
||||
// nanoseconds when the command identified by event is enqueued in
|
||||
// a command-queue by the host.
|
||||
ProfilingInfoCommandQueued ProfilingInfo = C.CL_PROFILING_COMMAND_QUEUED |
||||
// A 64-bit value that describes the current device time counter in
|
||||
// nanoseconds when the command identified by event that has been
|
||||
// enqueued is submitted by the host to the device associated with the command-queue.
|
||||
ProfilingInfoCommandSubmit ProfilingInfo = C.CL_PROFILING_COMMAND_SUBMIT |
||||
// A 64-bit value that describes the current device time counter in
|
||||
// nanoseconds when the command identified by event starts execution on the device.
|
||||
ProfilingInfoCommandStart ProfilingInfo = C.CL_PROFILING_COMMAND_START |
||||
// A 64-bit value that describes the current device time counter in
|
||||
// nanoseconds when the command identified by event has finished
|
||||
// execution on the device.
|
||||
ProfilingInfoCommandEnd ProfilingInfo = C.CL_PROFILING_COMMAND_END |
||||
) |
||||
|
||||
type CommmandExecStatus int |
||||
|
||||
const ( |
||||
CommmandExecStatusComplete CommmandExecStatus = C.CL_COMPLETE |
||||
CommmandExecStatusRunning CommmandExecStatus = C.CL_RUNNING |
||||
CommmandExecStatusSubmitted CommmandExecStatus = C.CL_SUBMITTED |
||||
CommmandExecStatusQueued CommmandExecStatus = C.CL_QUEUED |
||||
) |
||||
|
||||
type Event struct { |
||||
clEvent C.cl_event |
||||
} |
||||
|
||||
func releaseEvent(ev *Event) { |
||||
if ev.clEvent != nil { |
||||
C.clReleaseEvent(ev.clEvent) |
||||
ev.clEvent = nil |
||||
} |
||||
} |
||||
|
||||
func (e *Event) Release() { |
||||
releaseEvent(e) |
||||
} |
||||
|
||||
func (e *Event) GetEventProfilingInfo(paramName ProfilingInfo) (int64, error) { |
||||
var paramValue C.cl_ulong |
||||
if err := C.clGetEventProfilingInfo(e.clEvent, C.cl_profiling_info(paramName), C.size_t(unsafe.Sizeof(paramValue)), unsafe.Pointer(¶mValue), nil); err != C.CL_SUCCESS { |
||||
return 0, toError(err) |
||||
} |
||||
return int64(paramValue), nil |
||||
} |
||||
|
||||
// Sets the execution status of a user event object.
|
||||
//
|
||||
// `status` specifies the new execution status to be set and
|
||||
// can be CL_COMPLETE or a negative integer value to indicate
|
||||
// an error. A negative integer value causes all enqueued commands
|
||||
// that wait on this user event to be terminated. clSetUserEventStatus
|
||||
// can only be called once to change the execution status of event.
|
||||
func (e *Event) SetUserEventStatus(status int) error { |
||||
return toError(C.clSetUserEventStatus(e.clEvent, C.cl_int(status))) |
||||
} |
||||
|
||||
// Waits on the host thread for commands identified by event objects in
|
||||
// events to complete. A command is considered complete if its execution
|
||||
// status is CL_COMPLETE or a negative value. The events specified in
|
||||
// event_list act as synchronization points.
|
||||
//
|
||||
// If the cl_khr_gl_event extension is enabled, event objects can also be
|
||||
// used to reflect the status of an OpenGL sync object. The sync object
|
||||
// in turn refers to a fence command executing in an OpenGL command
|
||||
// stream. This provides another method of coordinating sharing of buffers
|
||||
// and images between OpenGL and OpenCL.
|
||||
func WaitForEvents(events []*Event) error { |
||||
return toError(C.clWaitForEvents(C.cl_uint(len(events)), eventListPtr(events))) |
||||
} |
||||
|
||||
func newEvent(clEvent C.cl_event) *Event { |
||||
ev := &Event{clEvent: clEvent} |
||||
runtime.SetFinalizer(ev, releaseEvent) |
||||
return ev |
||||
} |
||||
|
||||
func eventListPtr(el []*Event) *C.cl_event { |
||||
if el == nil { |
||||
return nil |
||||
} |
||||
elist := make([]C.cl_event, len(el)) |
||||
for i, e := range el { |
||||
elist[i] = e.clEvent |
||||
} |
||||
return (*C.cl_event)(&elist[0]) |
||||
} |
||||
|
||||
func clBool(b bool) C.cl_bool { |
||||
if b { |
||||
return C.CL_TRUE |
||||
} |
||||
return C.CL_FALSE |
||||
} |
||||
|
||||
func sizeT3(i3 [3]int) [3]C.size_t { |
||||
var val [3]C.size_t |
||||
val[0] = C.size_t(i3[0]) |
||||
val[1] = C.size_t(i3[1]) |
||||
val[2] = C.size_t(i3[2]) |
||||
return val |
||||
} |
||||
|
||||
type MappedMemObject struct { |
||||
ptr unsafe.Pointer |
||||
size int |
||||
rowPitch int |
||||
slicePitch int |
||||
} |
||||
|
||||
func (mb *MappedMemObject) ByteSlice() []byte { |
||||
var byteSlice []byte |
||||
sliceHeader := (*reflect.SliceHeader)(unsafe.Pointer(&byteSlice)) |
||||
sliceHeader.Cap = mb.size |
||||
sliceHeader.Len = mb.size |
||||
sliceHeader.Data = uintptr(mb.ptr) |
||||
return byteSlice |
||||
} |
||||
|
||||
func (mb *MappedMemObject) Ptr() unsafe.Pointer { |
||||
return mb.ptr |
||||
} |
||||
|
||||
func (mb *MappedMemObject) Size() int { |
||||
return mb.size |
||||
} |
||||
|
||||
func (mb *MappedMemObject) RowPitch() int { |
||||
return mb.rowPitch |
||||
} |
||||
|
||||
func (mb *MappedMemObject) SlicePitch() int { |
||||
return mb.slicePitch |
||||
} |
@ -0,0 +1,71 @@ |
||||
// +build cl12
|
||||
|
||||
package cl |
||||
|
||||
// #ifdef __APPLE__
|
||||
// #include "OpenCL/opencl.h"
|
||||
// #else
|
||||
// #include "cl.h"
|
||||
// #endif
|
||||
import "C" |
||||
|
||||
const ( |
||||
ChannelDataTypeUNormInt24 ChannelDataType = C.CL_UNORM_INT24 |
||||
ChannelOrderDepth ChannelOrder = C.CL_DEPTH |
||||
ChannelOrderDepthStencil ChannelOrder = C.CL_DEPTH_STENCIL |
||||
MemHostNoAccess MemFlag = C.CL_MEM_HOST_NO_ACCESS // OpenCL 1.2
|
||||
MemHostReadOnly MemFlag = C.CL_MEM_HOST_READ_ONLY // OpenCL 1.2
|
||||
MemHostWriteOnly MemFlag = C.CL_MEM_HOST_WRITE_ONLY // OpenCL 1.2
|
||||
MemObjectTypeImage1D MemObjectType = C.CL_MEM_OBJECT_IMAGE1D |
||||
MemObjectTypeImage1DArray MemObjectType = C.CL_MEM_OBJECT_IMAGE1D_ARRAY |
||||
MemObjectTypeImage1DBuffer MemObjectType = C.CL_MEM_OBJECT_IMAGE1D_BUFFER |
||||
MemObjectTypeImage2DArray MemObjectType = C.CL_MEM_OBJECT_IMAGE2D_ARRAY |
||||
// This flag specifies that the region being mapped in the memory object is being mapped for writing.
|
||||
//
|
||||
// The contents of the region being mapped are to be discarded. This is typically the case when the
|
||||
// region being mapped is overwritten by the host. This flag allows the implementation to no longer
|
||||
// guarantee that the pointer returned by clEnqueueMapBuffer or clEnqueueMapImage contains the
|
||||
// latest bits in the region being mapped which can be a significant performance enhancement.
|
||||
MapFlagWriteInvalidateRegion MapFlag = C.CL_MAP_WRITE_INVALIDATE_REGION |
||||
) |
||||
|
||||
func init() { |
||||
errorMap[C.CL_COMPILE_PROGRAM_FAILURE] = ErrCompileProgramFailure |
||||
errorMap[C.CL_DEVICE_PARTITION_FAILED] = ErrDevicePartitionFailed |
||||
errorMap[C.CL_INVALID_COMPILER_OPTIONS] = ErrInvalidCompilerOptions |
||||
errorMap[C.CL_INVALID_DEVICE_PARTITION_COUNT] = ErrInvalidDevicePartitionCount |
||||
errorMap[C.CL_INVALID_IMAGE_DESCRIPTOR] = ErrInvalidImageDescriptor |
||||
errorMap[C.CL_INVALID_LINKER_OPTIONS] = ErrInvalidLinkerOptions |
||||
errorMap[C.CL_KERNEL_ARG_INFO_NOT_AVAILABLE] = ErrKernelArgInfoNotAvailable |
||||
errorMap[C.CL_LINK_PROGRAM_FAILURE] = ErrLinkProgramFailure |
||||
errorMap[C.CL_LINKER_NOT_AVAILABLE] = ErrLinkerNotAvailable |
||||
channelOrderNameMap[ChannelOrderDepth] = "Depth" |
||||
channelOrderNameMap[ChannelOrderDepthStencil] = "DepthStencil" |
||||
channelDataTypeNameMap[ChannelDataTypeUNormInt24] = "UNormInt24" |
||||
} |
||||
|
||||
type ImageDescription struct { |
||||
Type MemObjectType |
||||
Width, Height, Depth int |
||||
ArraySize, RowPitch, SlicePitch int |
||||
NumMipLevels, NumSamples int |
||||
Buffer *MemObject |
||||
} |
||||
|
||||
func (d ImageDescription) toCl() C.cl_image_desc { |
||||
var desc C.cl_image_desc |
||||
desc.image_type = C.cl_mem_object_type(d.Type) |
||||
desc.image_width = C.size_t(d.Width) |
||||
desc.image_height = C.size_t(d.Height) |
||||
desc.image_depth = C.size_t(d.Depth) |
||||
desc.image_array_size = C.size_t(d.ArraySize) |
||||
desc.image_row_pitch = C.size_t(d.RowPitch) |
||||
desc.image_slice_pitch = C.size_t(d.SlicePitch) |
||||
desc.num_mip_levels = C.cl_uint(d.NumMipLevels) |
||||
desc.num_samples = C.cl_uint(d.NumSamples) |
||||
desc.buffer = nil |
||||
if d.Buffer != nil { |
||||
desc.buffer = d.Buffer.clMem |
||||
} |
||||
return desc |
||||
} |
45
Godeps/_workspace/src/github.com/Gustav-Simonsson/go-opencl/cl/types_darwin.go
generated
vendored
45
Godeps/_workspace/src/github.com/Gustav-Simonsson/go-opencl/cl/types_darwin.go
generated
vendored
@ -0,0 +1,45 @@ |
||||
package cl |
||||
|
||||
// #ifdef __APPLE__
|
||||
// #include "OpenCL/opencl.h"
|
||||
// #else
|
||||
// #include "cl.h"
|
||||
// #endif
|
||||
import "C" |
||||
|
||||
// Extension: cl_APPLE_fixed_alpha_channel_orders
|
||||
//
|
||||
// These selectors may be passed to clCreateImage2D() in the cl_image_format.image_channel_order field.
|
||||
// They are like CL_BGRA and CL_ARGB except that the alpha channel to be ignored. On calls to read_imagef,
|
||||
// the alpha will be 0xff (1.0f) if the sample falls in the image and 0 if it does not fall in the image.
|
||||
// On calls to write_imagef, the alpha value is ignored and 0xff (1.0f) is written. These formats are
|
||||
// currently only available for the CL_UNORM_INT8 cl_channel_type. They are intended to support legacy
|
||||
// image formats.
|
||||
const ( |
||||
ChannelOrder1RGBApple ChannelOrder = C.CL_1RGB_APPLE // Introduced in MacOS X.7.
|
||||
ChannelOrderBGR1Apple ChannelOrder = C.CL_BGR1_APPLE // Introduced in MacOS X.7.
|
||||
) |
||||
|
||||
// Extension: cl_APPLE_biased_fixed_point_image_formats
|
||||
//
|
||||
// This selector may be passed to clCreateImage2D() in the cl_image_format.image_channel_data_type field.
|
||||
// It defines a biased signed 1.14 fixed point storage format, with range [-1, 3). The conversion from
|
||||
// float to this fixed point format is defined as follows:
|
||||
//
|
||||
// ushort float_to_sfixed14( float x ){
|
||||
// int i = convert_int_sat_rte( x * 0x1.0p14f ); // scale [-1, 3.0) to [-16384, 3*16384), round to nearest integer
|
||||
// i = add_sat( i, 0x4000 ); // apply bias, to convert to [0, 65535) range
|
||||
// return convert_ushort_sat(i); // clamp to destination size
|
||||
// }
|
||||
//
|
||||
// The inverse conversion is the reverse process. The formats are currently only available on the CPU with
|
||||
// the CL_RGBA channel layout.
|
||||
const ( |
||||
ChannelDataTypeSFixed14Apple ChannelDataType = C.CL_SFIXED14_APPLE // Introduced in MacOS X.7.
|
||||
) |
||||
|
||||
func init() { |
||||
channelOrderNameMap[ChannelOrder1RGBApple] = "1RGBApple" |
||||
channelOrderNameMap[ChannelOrderBGR1Apple] = "RGB1Apple" |
||||
channelDataTypeNameMap[ChannelDataTypeSFixed14Apple] = "SFixed14Apple" |
||||
} |
@ -1,622 +0,0 @@ |
||||
package cli_test |
||||
|
||||
import ( |
||||
"flag" |
||||
"fmt" |
||||
"os" |
||||
"testing" |
||||
|
||||
"github.com/codegangsta/cli" |
||||
) |
||||
|
||||
func ExampleApp() { |
||||
// set args for examples sake
|
||||
os.Args = []string{"greet", "--name", "Jeremy"} |
||||
|
||||
app := cli.NewApp() |
||||
app.Name = "greet" |
||||
app.Flags = []cli.Flag{ |
||||
cli.StringFlag{Name: "name", Value: "bob", Usage: "a name to say"}, |
||||
} |
||||
app.Action = func(c *cli.Context) { |
||||
fmt.Printf("Hello %v\n", c.String("name")) |
||||
} |
||||
app.Author = "Harrison" |
||||
app.Email = "harrison@lolwut.com" |
||||
app.Authors = []cli.Author{cli.Author{Name: "Oliver Allen", Email: "oliver@toyshop.com"}} |
||||
app.Run(os.Args) |
||||
// Output:
|
||||
// Hello Jeremy
|
||||
} |
||||
|
||||
func ExampleAppSubcommand() { |
||||
// set args for examples sake
|
||||
os.Args = []string{"say", "hi", "english", "--name", "Jeremy"} |
||||
app := cli.NewApp() |
||||
app.Name = "say" |
||||
app.Commands = []cli.Command{ |
||||
{ |
||||
Name: "hello", |
||||
Aliases: []string{"hi"}, |
||||
Usage: "use it to see a description", |
||||
Description: "This is how we describe hello the function", |
||||
Subcommands: []cli.Command{ |
||||
{ |
||||
Name: "english", |
||||
Aliases: []string{"en"}, |
||||
Usage: "sends a greeting in english", |
||||
Description: "greets someone in english", |
||||
Flags: []cli.Flag{ |
||||
cli.StringFlag{ |
||||
Name: "name", |
||||
Value: "Bob", |
||||
Usage: "Name of the person to greet", |
||||
}, |
||||
}, |
||||
Action: func(c *cli.Context) { |
||||
fmt.Println("Hello,", c.String("name")) |
||||
}, |
||||
}, |
||||
}, |
||||
}, |
||||
} |
||||
|
||||
app.Run(os.Args) |
||||
// Output:
|
||||
// Hello, Jeremy
|
||||
} |
||||
|
||||
func ExampleAppHelp() { |
||||
// set args for examples sake
|
||||
os.Args = []string{"greet", "h", "describeit"} |
||||
|
||||
app := cli.NewApp() |
||||
app.Name = "greet" |
||||
app.Flags = []cli.Flag{ |
||||
cli.StringFlag{Name: "name", Value: "bob", Usage: "a name to say"}, |
||||
} |
||||
app.Commands = []cli.Command{ |
||||
{ |
||||
Name: "describeit", |
||||
Aliases: []string{"d"}, |
||||
Usage: "use it to see a description", |
||||
Description: "This is how we describe describeit the function", |
||||
Action: func(c *cli.Context) { |
||||
fmt.Printf("i like to describe things") |
||||
}, |
||||
}, |
||||
} |
||||
app.Run(os.Args) |
||||
// Output:
|
||||
// NAME:
|
||||
// describeit - use it to see a description
|
||||
//
|
||||
// USAGE:
|
||||
// command describeit [arguments...]
|
||||
//
|
||||
// DESCRIPTION:
|
||||
// This is how we describe describeit the function
|
||||
} |
||||
|
||||
func ExampleAppBashComplete() { |
||||
// set args for examples sake
|
||||
os.Args = []string{"greet", "--generate-bash-completion"} |
||||
|
||||
app := cli.NewApp() |
||||
app.Name = "greet" |
||||
app.EnableBashCompletion = true |
||||
app.Commands = []cli.Command{ |
||||
{ |
||||
Name: "describeit", |
||||
Aliases: []string{"d"}, |
||||
Usage: "use it to see a description", |
||||
Description: "This is how we describe describeit the function", |
||||
Action: func(c *cli.Context) { |
||||
fmt.Printf("i like to describe things") |
||||
}, |
||||
}, { |
||||
Name: "next", |
||||
Usage: "next example", |
||||
Description: "more stuff to see when generating bash completion", |
||||
Action: func(c *cli.Context) { |
||||
fmt.Printf("the next example") |
||||
}, |
||||
}, |
||||
} |
||||
|
||||
app.Run(os.Args) |
||||
// Output:
|
||||
// describeit
|
||||
// d
|
||||
// next
|
||||
// help
|
||||
// h
|
||||
} |
||||
|
||||
func TestApp_Run(t *testing.T) { |
||||
s := "" |
||||
|
||||
app := cli.NewApp() |
||||
app.Action = func(c *cli.Context) { |
||||
s = s + c.Args().First() |
||||
} |
||||
|
||||
err := app.Run([]string{"command", "foo"}) |
||||
expect(t, err, nil) |
||||
err = app.Run([]string{"command", "bar"}) |
||||
expect(t, err, nil) |
||||
expect(t, s, "foobar") |
||||
} |
||||
|
||||
var commandAppTests = []struct { |
||||
name string |
||||
expected bool |
||||
}{ |
||||
{"foobar", true}, |
||||
{"batbaz", true}, |
||||
{"b", true}, |
||||
{"f", true}, |
||||
{"bat", false}, |
||||
{"nothing", false}, |
||||
} |
||||
|
||||
func TestApp_Command(t *testing.T) { |
||||
app := cli.NewApp() |
||||
fooCommand := cli.Command{Name: "foobar", Aliases: []string{"f"}} |
||||
batCommand := cli.Command{Name: "batbaz", Aliases: []string{"b"}} |
||||
app.Commands = []cli.Command{ |
||||
fooCommand, |
||||
batCommand, |
||||
} |
||||
|
||||
for _, test := range commandAppTests { |
||||
expect(t, app.Command(test.name) != nil, test.expected) |
||||
} |
||||
} |
||||
|
||||
func TestApp_CommandWithArgBeforeFlags(t *testing.T) { |
||||
var parsedOption, firstArg string |
||||
|
||||
app := cli.NewApp() |
||||
command := cli.Command{ |
||||
Name: "cmd", |
||||
Flags: []cli.Flag{ |
||||
cli.StringFlag{Name: "option", Value: "", Usage: "some option"}, |
||||
}, |
||||
Action: func(c *cli.Context) { |
||||
parsedOption = c.String("option") |
||||
firstArg = c.Args().First() |
||||
}, |
||||
} |
||||
app.Commands = []cli.Command{command} |
||||
|
||||
app.Run([]string{"", "cmd", "my-arg", "--option", "my-option"}) |
||||
|
||||
expect(t, parsedOption, "my-option") |
||||
expect(t, firstArg, "my-arg") |
||||
} |
||||
|
||||
func TestApp_RunAsSubcommandParseFlags(t *testing.T) { |
||||
var context *cli.Context |
||||
|
||||
a := cli.NewApp() |
||||
a.Commands = []cli.Command{ |
||||
{ |
||||
Name: "foo", |
||||
Action: func(c *cli.Context) { |
||||
context = c |
||||
}, |
||||
Flags: []cli.Flag{ |
||||
cli.StringFlag{ |
||||
Name: "lang", |
||||
Value: "english", |
||||
Usage: "language for the greeting", |
||||
}, |
||||
}, |
||||
Before: func(_ *cli.Context) error { return nil }, |
||||
}, |
||||
} |
||||
a.Run([]string{"", "foo", "--lang", "spanish", "abcd"}) |
||||
|
||||
expect(t, context.Args().Get(0), "abcd") |
||||
expect(t, context.String("lang"), "spanish") |
||||
} |
||||
|
||||
func TestApp_CommandWithFlagBeforeTerminator(t *testing.T) { |
||||
var parsedOption string |
||||
var args []string |
||||
|
||||
app := cli.NewApp() |
||||
command := cli.Command{ |
||||
Name: "cmd", |
||||
Flags: []cli.Flag{ |
||||
cli.StringFlag{Name: "option", Value: "", Usage: "some option"}, |
||||
}, |
||||
Action: func(c *cli.Context) { |
||||
parsedOption = c.String("option") |
||||
args = c.Args() |
||||
}, |
||||
} |
||||
app.Commands = []cli.Command{command} |
||||
|
||||
app.Run([]string{"", "cmd", "my-arg", "--option", "my-option", "--", "--notARealFlag"}) |
||||
|
||||
expect(t, parsedOption, "my-option") |
||||
expect(t, args[0], "my-arg") |
||||
expect(t, args[1], "--") |
||||
expect(t, args[2], "--notARealFlag") |
||||
} |
||||
|
||||
func TestApp_CommandWithNoFlagBeforeTerminator(t *testing.T) { |
||||
var args []string |
||||
|
||||
app := cli.NewApp() |
||||
command := cli.Command{ |
||||
Name: "cmd", |
||||
Action: func(c *cli.Context) { |
||||
args = c.Args() |
||||
}, |
||||
} |
||||
app.Commands = []cli.Command{command} |
||||
|
||||
app.Run([]string{"", "cmd", "my-arg", "--", "notAFlagAtAll"}) |
||||
|
||||
expect(t, args[0], "my-arg") |
||||
expect(t, args[1], "--") |
||||
expect(t, args[2], "notAFlagAtAll") |
||||
} |
||||
|
||||
func TestApp_Float64Flag(t *testing.T) { |
||||
var meters float64 |
||||
|
||||
app := cli.NewApp() |
||||
app.Flags = []cli.Flag{ |
||||
cli.Float64Flag{Name: "height", Value: 1.5, Usage: "Set the height, in meters"}, |
||||
} |
||||
app.Action = func(c *cli.Context) { |
||||
meters = c.Float64("height") |
||||
} |
||||
|
||||
app.Run([]string{"", "--height", "1.93"}) |
||||
expect(t, meters, 1.93) |
||||
} |
||||
|
||||
func TestApp_ParseSliceFlags(t *testing.T) { |
||||
var parsedOption, firstArg string |
||||
var parsedIntSlice []int |
||||
var parsedStringSlice []string |
||||
|
||||
app := cli.NewApp() |
||||
command := cli.Command{ |
||||
Name: "cmd", |
||||
Flags: []cli.Flag{ |
||||
cli.IntSliceFlag{Name: "p", Value: &cli.IntSlice{}, Usage: "set one or more ip addr"}, |
||||
cli.StringSliceFlag{Name: "ip", Value: &cli.StringSlice{}, Usage: "set one or more ports to open"}, |
||||
}, |
||||
Action: func(c *cli.Context) { |
||||
parsedIntSlice = c.IntSlice("p") |
||||
parsedStringSlice = c.StringSlice("ip") |
||||
parsedOption = c.String("option") |
||||
firstArg = c.Args().First() |
||||
}, |
||||
} |
||||
app.Commands = []cli.Command{command} |
||||
|
||||
app.Run([]string{"", "cmd", "my-arg", "-p", "22", "-p", "80", "-ip", "8.8.8.8", "-ip", "8.8.4.4"}) |
||||
|
||||
IntsEquals := func(a, b []int) bool { |
||||
if len(a) != len(b) { |
||||
return false |
||||
} |
||||
for i, v := range a { |
||||
if v != b[i] { |
||||
return false |
||||
} |
||||
} |
||||
return true |
||||
} |
||||
|
||||
StrsEquals := func(a, b []string) bool { |
||||
if len(a) != len(b) { |
||||
return false |
||||
} |
||||
for i, v := range a { |
||||
if v != b[i] { |
||||
return false |
||||
} |
||||
} |
||||
return true |
||||
} |
||||
var expectedIntSlice = []int{22, 80} |
||||
var expectedStringSlice = []string{"8.8.8.8", "8.8.4.4"} |
||||
|
||||
if !IntsEquals(parsedIntSlice, expectedIntSlice) { |
||||
t.Errorf("%v does not match %v", parsedIntSlice, expectedIntSlice) |
||||
} |
||||
|
||||
if !StrsEquals(parsedStringSlice, expectedStringSlice) { |
||||
t.Errorf("%v does not match %v", parsedStringSlice, expectedStringSlice) |
||||
} |
||||
} |
||||
|
||||
func TestApp_DefaultStdout(t *testing.T) { |
||||
app := cli.NewApp() |
||||
|
||||
if app.Writer != os.Stdout { |
||||
t.Error("Default output writer not set.") |
||||
} |
||||
} |
||||
|
||||
type mockWriter struct { |
||||
written []byte |
||||
} |
||||
|
||||
func (fw *mockWriter) Write(p []byte) (n int, err error) { |
||||
if fw.written == nil { |
||||
fw.written = p |
||||
} else { |
||||
fw.written = append(fw.written, p...) |
||||
} |
||||
|
||||
return len(p), nil |
||||
} |
||||
|
||||
func (fw *mockWriter) GetWritten() (b []byte) { |
||||
return fw.written |
||||
} |
||||
|
||||
func TestApp_SetStdout(t *testing.T) { |
||||
w := &mockWriter{} |
||||
|
||||
app := cli.NewApp() |
||||
app.Name = "test" |
||||
app.Writer = w |
||||
|
||||
err := app.Run([]string{"help"}) |
||||
|
||||
if err != nil { |
||||
t.Fatalf("Run error: %s", err) |
||||
} |
||||
|
||||
if len(w.written) == 0 { |
||||
t.Error("App did not write output to desired writer.") |
||||
} |
||||
} |
||||
|
||||
func TestApp_BeforeFunc(t *testing.T) { |
||||
beforeRun, subcommandRun := false, false |
||||
beforeError := fmt.Errorf("fail") |
||||
var err error |
||||
|
||||
app := cli.NewApp() |
||||
|
||||
app.Before = func(c *cli.Context) error { |
||||
beforeRun = true |
||||
s := c.String("opt") |
||||
if s == "fail" { |
||||
return beforeError |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
app.Commands = []cli.Command{ |
||||
cli.Command{ |
||||
Name: "sub", |
||||
Action: func(c *cli.Context) { |
||||
subcommandRun = true |
||||
}, |
||||
}, |
||||
} |
||||
|
||||
app.Flags = []cli.Flag{ |
||||
cli.StringFlag{Name: "opt"}, |
||||
} |
||||
|
||||
// run with the Before() func succeeding
|
||||
err = app.Run([]string{"command", "--opt", "succeed", "sub"}) |
||||
|
||||
if err != nil { |
||||
t.Fatalf("Run error: %s", err) |
||||
} |
||||
|
||||
if beforeRun == false { |
||||
t.Errorf("Before() not executed when expected") |
||||
} |
||||
|
||||
if subcommandRun == false { |
||||
t.Errorf("Subcommand not executed when expected") |
||||
} |
||||
|
||||
// reset
|
||||
beforeRun, subcommandRun = false, false |
||||
|
||||
// run with the Before() func failing
|
||||
err = app.Run([]string{"command", "--opt", "fail", "sub"}) |
||||
|
||||
// should be the same error produced by the Before func
|
||||
if err != beforeError { |
||||
t.Errorf("Run error expected, but not received") |
||||
} |
||||
|
||||
if beforeRun == false { |
||||
t.Errorf("Before() not executed when expected") |
||||
} |
||||
|
||||
if subcommandRun == true { |
||||
t.Errorf("Subcommand executed when NOT expected") |
||||
} |
||||
|
||||
} |
||||
|
||||
func TestApp_AfterFunc(t *testing.T) { |
||||
afterRun, subcommandRun := false, false |
||||
afterError := fmt.Errorf("fail") |
||||
var err error |
||||
|
||||
app := cli.NewApp() |
||||
|
||||
app.After = func(c *cli.Context) error { |
||||
afterRun = true |
||||
s := c.String("opt") |
||||
if s == "fail" { |
||||
return afterError |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
app.Commands = []cli.Command{ |
||||
cli.Command{ |
||||
Name: "sub", |
||||
Action: func(c *cli.Context) { |
||||
subcommandRun = true |
||||
}, |
||||
}, |
||||
} |
||||
|
||||
app.Flags = []cli.Flag{ |
||||
cli.StringFlag{Name: "opt"}, |
||||
} |
||||
|
||||
// run with the After() func succeeding
|
||||
err = app.Run([]string{"command", "--opt", "succeed", "sub"}) |
||||
|
||||
if err != nil { |
||||
t.Fatalf("Run error: %s", err) |
||||
} |
||||
|
||||
if afterRun == false { |
||||
t.Errorf("After() not executed when expected") |
||||
} |
||||
|
||||
if subcommandRun == false { |
||||
t.Errorf("Subcommand not executed when expected") |
||||
} |
||||
|
||||
// reset
|
||||
afterRun, subcommandRun = false, false |
||||
|
||||
// run with the Before() func failing
|
||||
err = app.Run([]string{"command", "--opt", "fail", "sub"}) |
||||
|
||||
// should be the same error produced by the Before func
|
||||
if err != afterError { |
||||
t.Errorf("Run error expected, but not received") |
||||
} |
||||
|
||||
if afterRun == false { |
||||
t.Errorf("After() not executed when expected") |
||||
} |
||||
|
||||
if subcommandRun == false { |
||||
t.Errorf("Subcommand not executed when expected") |
||||
} |
||||
} |
||||
|
||||
func TestAppNoHelpFlag(t *testing.T) { |
||||
oldFlag := cli.HelpFlag |
||||
defer func() { |
||||
cli.HelpFlag = oldFlag |
||||
}() |
||||
|
||||
cli.HelpFlag = cli.BoolFlag{} |
||||
|
||||
app := cli.NewApp() |
||||
err := app.Run([]string{"test", "-h"}) |
||||
|
||||
if err != flag.ErrHelp { |
||||
t.Errorf("expected error about missing help flag, but got: %s (%T)", err, err) |
||||
} |
||||
} |
||||
|
||||
func TestAppHelpPrinter(t *testing.T) { |
||||
oldPrinter := cli.HelpPrinter |
||||
defer func() { |
||||
cli.HelpPrinter = oldPrinter |
||||
}() |
||||
|
||||
var wasCalled = false |
||||
cli.HelpPrinter = func(template string, data interface{}) { |
||||
wasCalled = true |
||||
} |
||||
|
||||
app := cli.NewApp() |
||||
app.Run([]string{"-h"}) |
||||
|
||||
if wasCalled == false { |
||||
t.Errorf("Help printer expected to be called, but was not") |
||||
} |
||||
} |
||||
|
||||
func TestAppVersionPrinter(t *testing.T) { |
||||
oldPrinter := cli.VersionPrinter |
||||
defer func() { |
||||
cli.VersionPrinter = oldPrinter |
||||
}() |
||||
|
||||
var wasCalled = false |
||||
cli.VersionPrinter = func(c *cli.Context) { |
||||
wasCalled = true |
||||
} |
||||
|
||||
app := cli.NewApp() |
||||
ctx := cli.NewContext(app, nil, nil) |
||||
cli.ShowVersion(ctx) |
||||
|
||||
if wasCalled == false { |
||||
t.Errorf("Version printer expected to be called, but was not") |
||||
} |
||||
} |
||||
|
||||
func TestAppCommandNotFound(t *testing.T) { |
||||
beforeRun, subcommandRun := false, false |
||||
app := cli.NewApp() |
||||
|
||||
app.CommandNotFound = func(c *cli.Context, command string) { |
||||
beforeRun = true |
||||
} |
||||
|
||||
app.Commands = []cli.Command{ |
||||
cli.Command{ |
||||
Name: "bar", |
||||
Action: func(c *cli.Context) { |
||||
subcommandRun = true |
||||
}, |
||||
}, |
||||
} |
||||
|
||||
app.Run([]string{"command", "foo"}) |
||||
|
||||
expect(t, beforeRun, true) |
||||
expect(t, subcommandRun, false) |
||||
} |
||||
|
||||
func TestGlobalFlagsInSubcommands(t *testing.T) { |
||||
subcommandRun := false |
||||
app := cli.NewApp() |
||||
|
||||
app.Flags = []cli.Flag{ |
||||
cli.BoolFlag{Name: "debug, d", Usage: "Enable debugging"}, |
||||
} |
||||
|
||||
app.Commands = []cli.Command{ |
||||
cli.Command{ |
||||
Name: "foo", |
||||
Subcommands: []cli.Command{ |
||||
{ |
||||
Name: "bar", |
||||
Action: func(c *cli.Context) { |
||||
if c.GlobalBool("debug") { |
||||
subcommandRun = true |
||||
} |
||||
}, |
||||
}, |
||||
}, |
||||
}, |
||||
} |
||||
|
||||
app.Run([]string{"command", "-d", "foo", "bar"}) |
||||
|
||||
expect(t, subcommandRun, true) |
||||
} |
2
Godeps/_workspace/src/github.com/codegangsta/cli/autocomplete/bash_autocomplete
generated
vendored
2
Godeps/_workspace/src/github.com/codegangsta/cli/autocomplete/bash_autocomplete
generated
vendored
@ -1,100 +0,0 @@ |
||||
package cli_test |
||||
|
||||
import ( |
||||
"os" |
||||
|
||||
"github.com/codegangsta/cli" |
||||
) |
||||
|
||||
func Example() { |
||||
app := cli.NewApp() |
||||
app.Name = "todo" |
||||
app.Usage = "task list on the command line" |
||||
app.Commands = []cli.Command{ |
||||
{ |
||||
Name: "add", |
||||
Aliases: []string{"a"}, |
||||
Usage: "add a task to the list", |
||||
Action: func(c *cli.Context) { |
||||
println("added task: ", c.Args().First()) |
||||
}, |
||||
}, |
||||
{ |
||||
Name: "complete", |
||||
Aliases: []string{"c"}, |
||||
Usage: "complete a task on the list", |
||||
Action: func(c *cli.Context) { |
||||
println("completed task: ", c.Args().First()) |
||||
}, |
||||
}, |
||||
} |
||||
|
||||
app.Run(os.Args) |
||||
} |
||||
|
||||
func ExampleSubcommand() { |
||||
app := cli.NewApp() |
||||
app.Name = "say" |
||||
app.Commands = []cli.Command{ |
||||
{ |
||||
Name: "hello", |
||||
Aliases: []string{"hi"}, |
||||
Usage: "use it to see a description", |
||||
Description: "This is how we describe hello the function", |
||||
Subcommands: []cli.Command{ |
||||
{ |
||||
Name: "english", |
||||
Aliases: []string{"en"}, |
||||
Usage: "sends a greeting in english", |
||||
Description: "greets someone in english", |
||||
Flags: []cli.Flag{ |
||||
cli.StringFlag{ |
||||
Name: "name", |
||||
Value: "Bob", |
||||
Usage: "Name of the person to greet", |
||||
}, |
||||
}, |
||||
Action: func(c *cli.Context) { |
||||
println("Hello, ", c.String("name")) |
||||
}, |
||||
}, { |
||||
Name: "spanish", |
||||
Aliases: []string{"sp"}, |
||||
Usage: "sends a greeting in spanish", |
||||
Flags: []cli.Flag{ |
||||
cli.StringFlag{ |
||||
Name: "surname", |
||||
Value: "Jones", |
||||
Usage: "Surname of the person to greet", |
||||
}, |
||||
}, |
||||
Action: func(c *cli.Context) { |
||||
println("Hola, ", c.String("surname")) |
||||
}, |
||||
}, { |
||||
Name: "french", |
||||
Aliases: []string{"fr"}, |
||||
Usage: "sends a greeting in french", |
||||
Flags: []cli.Flag{ |
||||
cli.StringFlag{ |
||||
Name: "nickname", |
||||
Value: "Stevie", |
||||
Usage: "Nickname of the person to greet", |
||||
}, |
||||
}, |
||||
Action: func(c *cli.Context) { |
||||
println("Bonjour, ", c.String("nickname")) |
||||
}, |
||||
}, |
||||
}, |
||||
}, { |
||||
Name: "bye", |
||||
Usage: "says goodbye", |
||||
Action: func(c *cli.Context) { |
||||
println("bye") |
||||
}, |
||||
}, |
||||
} |
||||
|
||||
app.Run(os.Args) |
||||
} |
@ -1,49 +0,0 @@ |
||||
package cli_test |
||||
|
||||
import ( |
||||
"flag" |
||||
"testing" |
||||
|
||||
"github.com/codegangsta/cli" |
||||
) |
||||
|
||||
func TestCommandDoNotIgnoreFlags(t *testing.T) { |
||||
app := cli.NewApp() |
||||
set := flag.NewFlagSet("test", 0) |
||||
test := []string{"blah", "blah", "-break"} |
||||
set.Parse(test) |
||||
|
||||
c := cli.NewContext(app, set, set) |
||||
|
||||
command := cli.Command{ |
||||
Name: "test-cmd", |
||||
Aliases: []string{"tc"}, |
||||
Usage: "this is for testing", |
||||
Description: "testing", |
||||
Action: func(_ *cli.Context) {}, |
||||
} |
||||
err := command.Run(c) |
||||
|
||||
expect(t, err.Error(), "flag provided but not defined: -break") |
||||
} |
||||
|
||||
func TestCommandIgnoreFlags(t *testing.T) { |
||||
app := cli.NewApp() |
||||
set := flag.NewFlagSet("test", 0) |
||||
test := []string{"blah", "blah"} |
||||
set.Parse(test) |
||||
|
||||
c := cli.NewContext(app, set, set) |
||||
|
||||
command := cli.Command{ |
||||
Name: "test-cmd", |
||||
Aliases: []string{"tc"}, |
||||
Usage: "this is for testing", |
||||
Description: "testing", |
||||
Action: func(_ *cli.Context) {}, |
||||
SkipFlagParsing: true, |
||||
} |
||||
err := command.Run(c) |
||||
|
||||
expect(t, err, nil) |
||||
} |
@ -1,111 +0,0 @@ |
||||
package cli_test |
||||
|
||||
import ( |
||||
"flag" |
||||
"testing" |
||||
"time" |
||||
|
||||
"github.com/codegangsta/cli" |
||||
) |
||||
|
||||
func TestNewContext(t *testing.T) { |
||||
set := flag.NewFlagSet("test", 0) |
||||
set.Int("myflag", 12, "doc") |
||||
globalSet := flag.NewFlagSet("test", 0) |
||||
globalSet.Int("myflag", 42, "doc") |
||||
command := cli.Command{Name: "mycommand"} |
||||
c := cli.NewContext(nil, set, globalSet) |
||||
c.Command = command |
||||
expect(t, c.Int("myflag"), 12) |
||||
expect(t, c.GlobalInt("myflag"), 42) |
||||
expect(t, c.Command.Name, "mycommand") |
||||
} |
||||
|
||||
func TestContext_Int(t *testing.T) { |
||||
set := flag.NewFlagSet("test", 0) |
||||
set.Int("myflag", 12, "doc") |
||||
c := cli.NewContext(nil, set, set) |
||||
expect(t, c.Int("myflag"), 12) |
||||
} |
||||
|
||||
func TestContext_Duration(t *testing.T) { |
||||
set := flag.NewFlagSet("test", 0) |
||||
set.Duration("myflag", time.Duration(12*time.Second), "doc") |
||||
c := cli.NewContext(nil, set, set) |
||||
expect(t, c.Duration("myflag"), time.Duration(12*time.Second)) |
||||
} |
||||
|
||||
func TestContext_String(t *testing.T) { |
||||
set := flag.NewFlagSet("test", 0) |
||||
set.String("myflag", "hello world", "doc") |
||||
c := cli.NewContext(nil, set, set) |
||||
expect(t, c.String("myflag"), "hello world") |
||||
} |
||||
|
||||
func TestContext_Bool(t *testing.T) { |
||||
set := flag.NewFlagSet("test", 0) |
||||
set.Bool("myflag", false, "doc") |
||||
c := cli.NewContext(nil, set, set) |
||||
expect(t, c.Bool("myflag"), false) |
||||
} |
||||
|
||||
func TestContext_BoolT(t *testing.T) { |
||||
set := flag.NewFlagSet("test", 0) |
||||
set.Bool("myflag", true, "doc") |
||||
c := cli.NewContext(nil, set, set) |
||||
expect(t, c.BoolT("myflag"), true) |
||||
} |
||||
|
||||
func TestContext_Args(t *testing.T) { |
||||
set := flag.NewFlagSet("test", 0) |
||||
set.Bool("myflag", false, "doc") |
||||
c := cli.NewContext(nil, set, set) |
||||
set.Parse([]string{"--myflag", "bat", "baz"}) |
||||
expect(t, len(c.Args()), 2) |
||||
expect(t, c.Bool("myflag"), true) |
||||
} |
||||
|
||||
func TestContext_IsSet(t *testing.T) { |
||||
set := flag.NewFlagSet("test", 0) |
||||
set.Bool("myflag", false, "doc") |
||||
set.String("otherflag", "hello world", "doc") |
||||
globalSet := flag.NewFlagSet("test", 0) |
||||
globalSet.Bool("myflagGlobal", true, "doc") |
||||
c := cli.NewContext(nil, set, globalSet) |
||||
set.Parse([]string{"--myflag", "bat", "baz"}) |
||||
globalSet.Parse([]string{"--myflagGlobal", "bat", "baz"}) |
||||
expect(t, c.IsSet("myflag"), true) |
||||
expect(t, c.IsSet("otherflag"), false) |
||||
expect(t, c.IsSet("bogusflag"), false) |
||||
expect(t, c.IsSet("myflagGlobal"), false) |
||||
} |
||||
|
||||
func TestContext_GlobalIsSet(t *testing.T) { |
||||
set := flag.NewFlagSet("test", 0) |
||||
set.Bool("myflag", false, "doc") |
||||
set.String("otherflag", "hello world", "doc") |
||||
globalSet := flag.NewFlagSet("test", 0) |
||||
globalSet.Bool("myflagGlobal", true, "doc") |
||||
globalSet.Bool("myflagGlobalUnset", true, "doc") |
||||
c := cli.NewContext(nil, set, globalSet) |
||||
set.Parse([]string{"--myflag", "bat", "baz"}) |
||||
globalSet.Parse([]string{"--myflagGlobal", "bat", "baz"}) |
||||
expect(t, c.GlobalIsSet("myflag"), false) |
||||
expect(t, c.GlobalIsSet("otherflag"), false) |
||||
expect(t, c.GlobalIsSet("bogusflag"), false) |
||||
expect(t, c.GlobalIsSet("myflagGlobal"), true) |
||||
expect(t, c.GlobalIsSet("myflagGlobalUnset"), false) |
||||
expect(t, c.GlobalIsSet("bogusGlobal"), false) |
||||
} |
||||
|
||||
func TestContext_NumFlags(t *testing.T) { |
||||
set := flag.NewFlagSet("test", 0) |
||||
set.Bool("myflag", false, "doc") |
||||
set.String("otherflag", "hello world", "doc") |
||||
globalSet := flag.NewFlagSet("test", 0) |
||||
globalSet.Bool("myflagGlobal", true, "doc") |
||||
c := cli.NewContext(nil, set, globalSet) |
||||
set.Parse([]string{"--myflag", "--otherflag=foo"}) |
||||
globalSet.Parse([]string{"--myflagGlobal"}) |
||||
expect(t, c.NumFlags(), 2) |
||||
} |
@ -1,742 +0,0 @@ |
||||
package cli_test |
||||
|
||||
import ( |
||||
"fmt" |
||||
"os" |
||||
"reflect" |
||||
"strings" |
||||
"testing" |
||||
|
||||
"github.com/codegangsta/cli" |
||||
) |
||||
|
||||
var boolFlagTests = []struct { |
||||
name string |
||||
expected string |
||||
}{ |
||||
{"help", "--help\t"}, |
||||
{"h", "-h\t"}, |
||||
} |
||||
|
||||
func TestBoolFlagHelpOutput(t *testing.T) { |
||||
|
||||
for _, test := range boolFlagTests { |
||||
flag := cli.BoolFlag{Name: test.name} |
||||
output := flag.String() |
||||
|
||||
if output != test.expected { |
||||
t.Errorf("%s does not match %s", output, test.expected) |
||||
} |
||||
} |
||||
} |
||||
|
||||
var stringFlagTests = []struct { |
||||
name string |
||||
value string |
||||
expected string |
||||
}{ |
||||
{"help", "", "--help \t"}, |
||||
{"h", "", "-h \t"}, |
||||
{"h", "", "-h \t"}, |
||||
{"test", "Something", "--test \"Something\"\t"}, |
||||
} |
||||
|
||||
func TestStringFlagHelpOutput(t *testing.T) { |
||||
|
||||
for _, test := range stringFlagTests { |
||||
flag := cli.StringFlag{Name: test.name, Value: test.value} |
||||
output := flag.String() |
||||
|
||||
if output != test.expected { |
||||
t.Errorf("%s does not match %s", output, test.expected) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestStringFlagWithEnvVarHelpOutput(t *testing.T) { |
||||
os.Clearenv() |
||||
os.Setenv("APP_FOO", "derp") |
||||
for _, test := range stringFlagTests { |
||||
flag := cli.StringFlag{Name: test.name, Value: test.value, EnvVar: "APP_FOO"} |
||||
output := flag.String() |
||||
|
||||
if !strings.HasSuffix(output, " [$APP_FOO]") { |
||||
t.Errorf("%s does not end with [$APP_FOO]", output) |
||||
} |
||||
} |
||||
} |
||||
|
||||
var stringSliceFlagTests = []struct { |
||||
name string |
||||
value *cli.StringSlice |
||||
expected string |
||||
}{ |
||||
{"help", func() *cli.StringSlice { |
||||
s := &cli.StringSlice{} |
||||
s.Set("") |
||||
return s |
||||
}(), "--help [--help option --help option]\t"}, |
||||
{"h", func() *cli.StringSlice { |
||||
s := &cli.StringSlice{} |
||||
s.Set("") |
||||
return s |
||||
}(), "-h [-h option -h option]\t"}, |
||||
{"h", func() *cli.StringSlice { |
||||
s := &cli.StringSlice{} |
||||
s.Set("") |
||||
return s |
||||
}(), "-h [-h option -h option]\t"}, |
||||
{"test", func() *cli.StringSlice { |
||||
s := &cli.StringSlice{} |
||||
s.Set("Something") |
||||
return s |
||||
}(), "--test [--test option --test option]\t"}, |
||||
} |
||||
|
||||
func TestStringSliceFlagHelpOutput(t *testing.T) { |
||||
|
||||
for _, test := range stringSliceFlagTests { |
||||
flag := cli.StringSliceFlag{Name: test.name, Value: test.value} |
||||
output := flag.String() |
||||
|
||||
if output != test.expected { |
||||
t.Errorf("%q does not match %q", output, test.expected) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestStringSliceFlagWithEnvVarHelpOutput(t *testing.T) { |
||||
os.Clearenv() |
||||
os.Setenv("APP_QWWX", "11,4") |
||||
for _, test := range stringSliceFlagTests { |
||||
flag := cli.StringSliceFlag{Name: test.name, Value: test.value, EnvVar: "APP_QWWX"} |
||||
output := flag.String() |
||||
|
||||
if !strings.HasSuffix(output, " [$APP_QWWX]") { |
||||
t.Errorf("%q does not end with [$APP_QWWX]", output) |
||||
} |
||||
} |
||||
} |
||||
|
||||
var intFlagTests = []struct { |
||||
name string |
||||
expected string |
||||
}{ |
||||
{"help", "--help \"0\"\t"}, |
||||
{"h", "-h \"0\"\t"}, |
||||
} |
||||
|
||||
func TestIntFlagHelpOutput(t *testing.T) { |
||||
|
||||
for _, test := range intFlagTests { |
||||
flag := cli.IntFlag{Name: test.name} |
||||
output := flag.String() |
||||
|
||||
if output != test.expected { |
||||
t.Errorf("%s does not match %s", output, test.expected) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestIntFlagWithEnvVarHelpOutput(t *testing.T) { |
||||
os.Clearenv() |
||||
os.Setenv("APP_BAR", "2") |
||||
for _, test := range intFlagTests { |
||||
flag := cli.IntFlag{Name: test.name, EnvVar: "APP_BAR"} |
||||
output := flag.String() |
||||
|
||||
if !strings.HasSuffix(output, " [$APP_BAR]") { |
||||
t.Errorf("%s does not end with [$APP_BAR]", output) |
||||
} |
||||
} |
||||
} |
||||
|
||||
var durationFlagTests = []struct { |
||||
name string |
||||
expected string |
||||
}{ |
||||
{"help", "--help \"0\"\t"}, |
||||
{"h", "-h \"0\"\t"}, |
||||
} |
||||
|
||||
func TestDurationFlagHelpOutput(t *testing.T) { |
||||
|
||||
for _, test := range durationFlagTests { |
||||
flag := cli.DurationFlag{Name: test.name} |
||||
output := flag.String() |
||||
|
||||
if output != test.expected { |
||||
t.Errorf("%s does not match %s", output, test.expected) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestDurationFlagWithEnvVarHelpOutput(t *testing.T) { |
||||
os.Clearenv() |
||||
os.Setenv("APP_BAR", "2h3m6s") |
||||
for _, test := range durationFlagTests { |
||||
flag := cli.DurationFlag{Name: test.name, EnvVar: "APP_BAR"} |
||||
output := flag.String() |
||||
|
||||
if !strings.HasSuffix(output, " [$APP_BAR]") { |
||||
t.Errorf("%s does not end with [$APP_BAR]", output) |
||||
} |
||||
} |
||||
} |
||||
|
||||
var intSliceFlagTests = []struct { |
||||
name string |
||||
value *cli.IntSlice |
||||
expected string |
||||
}{ |
||||
{"help", &cli.IntSlice{}, "--help [--help option --help option]\t"}, |
||||
{"h", &cli.IntSlice{}, "-h [-h option -h option]\t"}, |
||||
{"h", &cli.IntSlice{}, "-h [-h option -h option]\t"}, |
||||
{"test", func() *cli.IntSlice { |
||||
i := &cli.IntSlice{} |
||||
i.Set("9") |
||||
return i |
||||
}(), "--test [--test option --test option]\t"}, |
||||
} |
||||
|
||||
func TestIntSliceFlagHelpOutput(t *testing.T) { |
||||
|
||||
for _, test := range intSliceFlagTests { |
||||
flag := cli.IntSliceFlag{Name: test.name, Value: test.value} |
||||
output := flag.String() |
||||
|
||||
if output != test.expected { |
||||
t.Errorf("%q does not match %q", output, test.expected) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestIntSliceFlagWithEnvVarHelpOutput(t *testing.T) { |
||||
os.Clearenv() |
||||
os.Setenv("APP_SMURF", "42,3") |
||||
for _, test := range intSliceFlagTests { |
||||
flag := cli.IntSliceFlag{Name: test.name, Value: test.value, EnvVar: "APP_SMURF"} |
||||
output := flag.String() |
||||
|
||||
if !strings.HasSuffix(output, " [$APP_SMURF]") { |
||||
t.Errorf("%q does not end with [$APP_SMURF]", output) |
||||
} |
||||
} |
||||
} |
||||
|
||||
var float64FlagTests = []struct { |
||||
name string |
||||
expected string |
||||
}{ |
||||
{"help", "--help \"0\"\t"}, |
||||
{"h", "-h \"0\"\t"}, |
||||
} |
||||
|
||||
func TestFloat64FlagHelpOutput(t *testing.T) { |
||||
|
||||
for _, test := range float64FlagTests { |
||||
flag := cli.Float64Flag{Name: test.name} |
||||
output := flag.String() |
||||
|
||||
if output != test.expected { |
||||
t.Errorf("%s does not match %s", output, test.expected) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestFloat64FlagWithEnvVarHelpOutput(t *testing.T) { |
||||
os.Clearenv() |
||||
os.Setenv("APP_BAZ", "99.4") |
||||
for _, test := range float64FlagTests { |
||||
flag := cli.Float64Flag{Name: test.name, EnvVar: "APP_BAZ"} |
||||
output := flag.String() |
||||
|
||||
if !strings.HasSuffix(output, " [$APP_BAZ]") { |
||||
t.Errorf("%s does not end with [$APP_BAZ]", output) |
||||
} |
||||
} |
||||
} |
||||
|
||||
var genericFlagTests = []struct { |
||||
name string |
||||
value cli.Generic |
||||
expected string |
||||
}{ |
||||
{"test", &Parser{"abc", "def"}, "--test \"abc,def\"\ttest flag"}, |
||||
{"t", &Parser{"abc", "def"}, "-t \"abc,def\"\ttest flag"}, |
||||
} |
||||
|
||||
func TestGenericFlagHelpOutput(t *testing.T) { |
||||
|
||||
for _, test := range genericFlagTests { |
||||
flag := cli.GenericFlag{Name: test.name, Value: test.value, Usage: "test flag"} |
||||
output := flag.String() |
||||
|
||||
if output != test.expected { |
||||
t.Errorf("%q does not match %q", output, test.expected) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestGenericFlagWithEnvVarHelpOutput(t *testing.T) { |
||||
os.Clearenv() |
||||
os.Setenv("APP_ZAP", "3") |
||||
for _, test := range genericFlagTests { |
||||
flag := cli.GenericFlag{Name: test.name, EnvVar: "APP_ZAP"} |
||||
output := flag.String() |
||||
|
||||
if !strings.HasSuffix(output, " [$APP_ZAP]") { |
||||
t.Errorf("%s does not end with [$APP_ZAP]", output) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestParseMultiString(t *testing.T) { |
||||
(&cli.App{ |
||||
Flags: []cli.Flag{ |
||||
cli.StringFlag{Name: "serve, s"}, |
||||
}, |
||||
Action: func(ctx *cli.Context) { |
||||
if ctx.String("serve") != "10" { |
||||
t.Errorf("main name not set") |
||||
} |
||||
if ctx.String("s") != "10" { |
||||
t.Errorf("short name not set") |
||||
} |
||||
}, |
||||
}).Run([]string{"run", "-s", "10"}) |
||||
} |
||||
|
||||
func TestParseMultiStringFromEnv(t *testing.T) { |
||||
os.Clearenv() |
||||
os.Setenv("APP_COUNT", "20") |
||||
(&cli.App{ |
||||
Flags: []cli.Flag{ |
||||
cli.StringFlag{Name: "count, c", EnvVar: "APP_COUNT"}, |
||||
}, |
||||
Action: func(ctx *cli.Context) { |
||||
if ctx.String("count") != "20" { |
||||
t.Errorf("main name not set") |
||||
} |
||||
if ctx.String("c") != "20" { |
||||
t.Errorf("short name not set") |
||||
} |
||||
}, |
||||
}).Run([]string{"run"}) |
||||
} |
||||
|
||||
func TestParseMultiStringFromEnvCascade(t *testing.T) { |
||||
os.Clearenv() |
||||
os.Setenv("APP_COUNT", "20") |
||||
(&cli.App{ |
||||
Flags: []cli.Flag{ |
||||
cli.StringFlag{Name: "count, c", EnvVar: "COMPAT_COUNT,APP_COUNT"}, |
||||
}, |
||||
Action: func(ctx *cli.Context) { |
||||
if ctx.String("count") != "20" { |
||||
t.Errorf("main name not set") |
||||
} |
||||
if ctx.String("c") != "20" { |
||||
t.Errorf("short name not set") |
||||
} |
||||
}, |
||||
}).Run([]string{"run"}) |
||||
} |
||||
|
||||
func TestParseMultiStringSlice(t *testing.T) { |
||||
(&cli.App{ |
||||
Flags: []cli.Flag{ |
||||
cli.StringSliceFlag{Name: "serve, s", Value: &cli.StringSlice{}}, |
||||
}, |
||||
Action: func(ctx *cli.Context) { |
||||
if !reflect.DeepEqual(ctx.StringSlice("serve"), []string{"10", "20"}) { |
||||
t.Errorf("main name not set") |
||||
} |
||||
if !reflect.DeepEqual(ctx.StringSlice("s"), []string{"10", "20"}) { |
||||
t.Errorf("short name not set") |
||||
} |
||||
}, |
||||
}).Run([]string{"run", "-s", "10", "-s", "20"}) |
||||
} |
||||
|
||||
func TestParseMultiStringSliceFromEnv(t *testing.T) { |
||||
os.Clearenv() |
||||
os.Setenv("APP_INTERVALS", "20,30,40") |
||||
|
||||
(&cli.App{ |
||||
Flags: []cli.Flag{ |
||||
cli.StringSliceFlag{Name: "intervals, i", Value: &cli.StringSlice{}, EnvVar: "APP_INTERVALS"}, |
||||
}, |
||||
Action: func(ctx *cli.Context) { |
||||
if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) { |
||||
t.Errorf("main name not set from env") |
||||
} |
||||
if !reflect.DeepEqual(ctx.StringSlice("i"), []string{"20", "30", "40"}) { |
||||
t.Errorf("short name not set from env") |
||||
} |
||||
}, |
||||
}).Run([]string{"run"}) |
||||
} |
||||
|
||||
func TestParseMultiStringSliceFromEnvCascade(t *testing.T) { |
||||
os.Clearenv() |
||||
os.Setenv("APP_INTERVALS", "20,30,40") |
||||
|
||||
(&cli.App{ |
||||
Flags: []cli.Flag{ |
||||
cli.StringSliceFlag{Name: "intervals, i", Value: &cli.StringSlice{}, EnvVar: "COMPAT_INTERVALS,APP_INTERVALS"}, |
||||
}, |
||||
Action: func(ctx *cli.Context) { |
||||
if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) { |
||||
t.Errorf("main name not set from env") |
||||
} |
||||
if !reflect.DeepEqual(ctx.StringSlice("i"), []string{"20", "30", "40"}) { |
||||
t.Errorf("short name not set from env") |
||||
} |
||||
}, |
||||
}).Run([]string{"run"}) |
||||
} |
||||
|
||||
func TestParseMultiInt(t *testing.T) { |
||||
a := cli.App{ |
||||
Flags: []cli.Flag{ |
||||
cli.IntFlag{Name: "serve, s"}, |
||||
}, |
||||
Action: func(ctx *cli.Context) { |
||||
if ctx.Int("serve") != 10 { |
||||
t.Errorf("main name not set") |
||||
} |
||||
if ctx.Int("s") != 10 { |
||||
t.Errorf("short name not set") |
||||
} |
||||
}, |
||||
} |
||||
a.Run([]string{"run", "-s", "10"}) |
||||
} |
||||
|
||||
func TestParseMultiIntFromEnv(t *testing.T) { |
||||
os.Clearenv() |
||||
os.Setenv("APP_TIMEOUT_SECONDS", "10") |
||||
a := cli.App{ |
||||
Flags: []cli.Flag{ |
||||
cli.IntFlag{Name: "timeout, t", EnvVar: "APP_TIMEOUT_SECONDS"}, |
||||
}, |
||||
Action: func(ctx *cli.Context) { |
||||
if ctx.Int("timeout") != 10 { |
||||
t.Errorf("main name not set") |
||||
} |
||||
if ctx.Int("t") != 10 { |
||||
t.Errorf("short name not set") |
||||
} |
||||
}, |
||||
} |
||||
a.Run([]string{"run"}) |
||||
} |
||||
|
||||
func TestParseMultiIntFromEnvCascade(t *testing.T) { |
||||
os.Clearenv() |
||||
os.Setenv("APP_TIMEOUT_SECONDS", "10") |
||||
a := cli.App{ |
||||
Flags: []cli.Flag{ |
||||
cli.IntFlag{Name: "timeout, t", EnvVar: "COMPAT_TIMEOUT_SECONDS,APP_TIMEOUT_SECONDS"}, |
||||
}, |
||||
Action: func(ctx *cli.Context) { |
||||
if ctx.Int("timeout") != 10 { |
||||
t.Errorf("main name not set") |
||||
} |
||||
if ctx.Int("t") != 10 { |
||||
t.Errorf("short name not set") |
||||
} |
||||
}, |
||||
} |
||||
a.Run([]string{"run"}) |
||||
} |
||||
|
||||
func TestParseMultiIntSlice(t *testing.T) { |
||||
(&cli.App{ |
||||
Flags: []cli.Flag{ |
||||
cli.IntSliceFlag{Name: "serve, s", Value: &cli.IntSlice{}}, |
||||
}, |
||||
Action: func(ctx *cli.Context) { |
||||
if !reflect.DeepEqual(ctx.IntSlice("serve"), []int{10, 20}) { |
||||
t.Errorf("main name not set") |
||||
} |
||||
if !reflect.DeepEqual(ctx.IntSlice("s"), []int{10, 20}) { |
||||
t.Errorf("short name not set") |
||||
} |
||||
}, |
||||
}).Run([]string{"run", "-s", "10", "-s", "20"}) |
||||
} |
||||
|
||||
func TestParseMultiIntSliceFromEnv(t *testing.T) { |
||||
os.Clearenv() |
||||
os.Setenv("APP_INTERVALS", "20,30,40") |
||||
|
||||
(&cli.App{ |
||||
Flags: []cli.Flag{ |
||||
cli.IntSliceFlag{Name: "intervals, i", Value: &cli.IntSlice{}, EnvVar: "APP_INTERVALS"}, |
||||
}, |
||||
Action: func(ctx *cli.Context) { |
||||
if !reflect.DeepEqual(ctx.IntSlice("intervals"), []int{20, 30, 40}) { |
||||
t.Errorf("main name not set from env") |
||||
} |
||||
if !reflect.DeepEqual(ctx.IntSlice("i"), []int{20, 30, 40}) { |
||||
t.Errorf("short name not set from env") |
||||
} |
||||
}, |
||||
}).Run([]string{"run"}) |
||||
} |
||||
|
||||
func TestParseMultiIntSliceFromEnvCascade(t *testing.T) { |
||||
os.Clearenv() |
||||
os.Setenv("APP_INTERVALS", "20,30,40") |
||||
|
||||
(&cli.App{ |
||||
Flags: []cli.Flag{ |
||||
cli.IntSliceFlag{Name: "intervals, i", Value: &cli.IntSlice{}, EnvVar: "COMPAT_INTERVALS,APP_INTERVALS"}, |
||||
}, |
||||
Action: func(ctx *cli.Context) { |
||||
if !reflect.DeepEqual(ctx.IntSlice("intervals"), []int{20, 30, 40}) { |
||||
t.Errorf("main name not set from env") |
||||
} |
||||
if !reflect.DeepEqual(ctx.IntSlice("i"), []int{20, 30, 40}) { |
||||
t.Errorf("short name not set from env") |
||||
} |
||||
}, |
||||
}).Run([]string{"run"}) |
||||
} |
||||
|
||||
func TestParseMultiFloat64(t *testing.T) { |
||||
a := cli.App{ |
||||
Flags: []cli.Flag{ |
||||
cli.Float64Flag{Name: "serve, s"}, |
||||
}, |
||||
Action: func(ctx *cli.Context) { |
||||
if ctx.Float64("serve") != 10.2 { |
||||
t.Errorf("main name not set") |
||||
} |
||||
if ctx.Float64("s") != 10.2 { |
||||
t.Errorf("short name not set") |
||||
} |
||||
}, |
||||
} |
||||
a.Run([]string{"run", "-s", "10.2"}) |
||||
} |
||||
|
||||
func TestParseMultiFloat64FromEnv(t *testing.T) { |
||||
os.Clearenv() |
||||
os.Setenv("APP_TIMEOUT_SECONDS", "15.5") |
||||
a := cli.App{ |
||||
Flags: []cli.Flag{ |
||||
cli.Float64Flag{Name: "timeout, t", EnvVar: "APP_TIMEOUT_SECONDS"}, |
||||
}, |
||||
Action: func(ctx *cli.Context) { |
||||
if ctx.Float64("timeout") != 15.5 { |
||||
t.Errorf("main name not set") |
||||
} |
||||
if ctx.Float64("t") != 15.5 { |
||||
t.Errorf("short name not set") |
||||
} |
||||
}, |
||||
} |
||||
a.Run([]string{"run"}) |
||||
} |
||||
|
||||
func TestParseMultiFloat64FromEnvCascade(t *testing.T) { |
||||
os.Clearenv() |
||||
os.Setenv("APP_TIMEOUT_SECONDS", "15.5") |
||||
a := cli.App{ |
||||
Flags: []cli.Flag{ |
||||
cli.Float64Flag{Name: "timeout, t", EnvVar: "COMPAT_TIMEOUT_SECONDS,APP_TIMEOUT_SECONDS"}, |
||||
}, |
||||
Action: func(ctx *cli.Context) { |
||||
if ctx.Float64("timeout") != 15.5 { |
||||
t.Errorf("main name not set") |
||||
} |
||||
if ctx.Float64("t") != 15.5 { |
||||
t.Errorf("short name not set") |
||||
} |
||||
}, |
||||
} |
||||
a.Run([]string{"run"}) |
||||
} |
||||
|
||||
func TestParseMultiBool(t *testing.T) { |
||||
a := cli.App{ |
||||
Flags: []cli.Flag{ |
||||
cli.BoolFlag{Name: "serve, s"}, |
||||
}, |
||||
Action: func(ctx *cli.Context) { |
||||
if ctx.Bool("serve") != true { |
||||
t.Errorf("main name not set") |
||||
} |
||||
if ctx.Bool("s") != true { |
||||
t.Errorf("short name not set") |
||||
} |
||||
}, |
||||
} |
||||
a.Run([]string{"run", "--serve"}) |
||||
} |
||||
|
||||
func TestParseMultiBoolFromEnv(t *testing.T) { |
||||
os.Clearenv() |
||||
os.Setenv("APP_DEBUG", "1") |
||||
a := cli.App{ |
||||
Flags: []cli.Flag{ |
||||
cli.BoolFlag{Name: "debug, d", EnvVar: "APP_DEBUG"}, |
||||
}, |
||||
Action: func(ctx *cli.Context) { |
||||
if ctx.Bool("debug") != true { |
||||
t.Errorf("main name not set from env") |
||||
} |
||||
if ctx.Bool("d") != true { |
||||
t.Errorf("short name not set from env") |
||||
} |
||||
}, |
||||
} |
||||
a.Run([]string{"run"}) |
||||
} |
||||
|
||||
func TestParseMultiBoolFromEnvCascade(t *testing.T) { |
||||
os.Clearenv() |
||||
os.Setenv("APP_DEBUG", "1") |
||||
a := cli.App{ |
||||
Flags: []cli.Flag{ |
||||
cli.BoolFlag{Name: "debug, d", EnvVar: "COMPAT_DEBUG,APP_DEBUG"}, |
||||
}, |
||||
Action: func(ctx *cli.Context) { |
||||
if ctx.Bool("debug") != true { |
||||
t.Errorf("main name not set from env") |
||||
} |
||||
if ctx.Bool("d") != true { |
||||
t.Errorf("short name not set from env") |
||||
} |
||||
}, |
||||
} |
||||
a.Run([]string{"run"}) |
||||
} |
||||
|
||||
func TestParseMultiBoolT(t *testing.T) { |
||||
a := cli.App{ |
||||
Flags: []cli.Flag{ |
||||
cli.BoolTFlag{Name: "serve, s"}, |
||||
}, |
||||
Action: func(ctx *cli.Context) { |
||||
if ctx.BoolT("serve") != true { |
||||
t.Errorf("main name not set") |
||||
} |
||||
if ctx.BoolT("s") != true { |
||||
t.Errorf("short name not set") |
||||
} |
||||
}, |
||||
} |
||||
a.Run([]string{"run", "--serve"}) |
||||
} |
||||
|
||||
func TestParseMultiBoolTFromEnv(t *testing.T) { |
||||
os.Clearenv() |
||||
os.Setenv("APP_DEBUG", "0") |
||||
a := cli.App{ |
||||
Flags: []cli.Flag{ |
||||
cli.BoolTFlag{Name: "debug, d", EnvVar: "APP_DEBUG"}, |
||||
}, |
||||
Action: func(ctx *cli.Context) { |
||||
if ctx.BoolT("debug") != false { |
||||
t.Errorf("main name not set from env") |
||||
} |
||||
if ctx.BoolT("d") != false { |
||||
t.Errorf("short name not set from env") |
||||
} |
||||
}, |
||||
} |
||||
a.Run([]string{"run"}) |
||||
} |
||||
|
||||
func TestParseMultiBoolTFromEnvCascade(t *testing.T) { |
||||
os.Clearenv() |
||||
os.Setenv("APP_DEBUG", "0") |
||||
a := cli.App{ |
||||
Flags: []cli.Flag{ |
||||
cli.BoolTFlag{Name: "debug, d", EnvVar: "COMPAT_DEBUG,APP_DEBUG"}, |
||||
}, |
||||
Action: func(ctx *cli.Context) { |
||||
if ctx.BoolT("debug") != false { |
||||
t.Errorf("main name not set from env") |
||||
} |
||||
if ctx.BoolT("d") != false { |
||||
t.Errorf("short name not set from env") |
||||
} |
||||
}, |
||||
} |
||||
a.Run([]string{"run"}) |
||||
} |
||||
|
||||
type Parser [2]string |
||||
|
||||
func (p *Parser) Set(value string) error { |
||||
parts := strings.Split(value, ",") |
||||
if len(parts) != 2 { |
||||
return fmt.Errorf("invalid format") |
||||
} |
||||
|
||||
(*p)[0] = parts[0] |
||||
(*p)[1] = parts[1] |
||||
|
||||
return nil |
||||
} |
||||
|
||||
func (p *Parser) String() string { |
||||
return fmt.Sprintf("%s,%s", p[0], p[1]) |
||||
} |
||||
|
||||
func TestParseGeneric(t *testing.T) { |
||||
a := cli.App{ |
||||
Flags: []cli.Flag{ |
||||
cli.GenericFlag{Name: "serve, s", Value: &Parser{}}, |
||||
}, |
||||
Action: func(ctx *cli.Context) { |
||||
if !reflect.DeepEqual(ctx.Generic("serve"), &Parser{"10", "20"}) { |
||||
t.Errorf("main name not set") |
||||
} |
||||
if !reflect.DeepEqual(ctx.Generic("s"), &Parser{"10", "20"}) { |
||||
t.Errorf("short name not set") |
||||
} |
||||
}, |
||||
} |
||||
a.Run([]string{"run", "-s", "10,20"}) |
||||
} |
||||
|
||||
func TestParseGenericFromEnv(t *testing.T) { |
||||
os.Clearenv() |
||||
os.Setenv("APP_SERVE", "20,30") |
||||
a := cli.App{ |
||||
Flags: []cli.Flag{ |
||||
cli.GenericFlag{Name: "serve, s", Value: &Parser{}, EnvVar: "APP_SERVE"}, |
||||
}, |
||||
Action: func(ctx *cli.Context) { |
||||
if !reflect.DeepEqual(ctx.Generic("serve"), &Parser{"20", "30"}) { |
||||
t.Errorf("main name not set from env") |
||||
} |
||||
if !reflect.DeepEqual(ctx.Generic("s"), &Parser{"20", "30"}) { |
||||
t.Errorf("short name not set from env") |
||||
} |
||||
}, |
||||
} |
||||
a.Run([]string{"run"}) |
||||
} |
||||
|
||||
func TestParseGenericFromEnvCascade(t *testing.T) { |
||||
os.Clearenv() |
||||
os.Setenv("APP_FOO", "99,2000") |
||||
a := cli.App{ |
||||
Flags: []cli.Flag{ |
||||
cli.GenericFlag{Name: "foos", Value: &Parser{}, EnvVar: "COMPAT_FOO,APP_FOO"}, |
||||
}, |
||||
Action: func(ctx *cli.Context) { |
||||
if !reflect.DeepEqual(ctx.Generic("foos"), &Parser{"99", "2000"}) { |
||||
t.Errorf("value not set from env") |
||||
} |
||||
}, |
||||
} |
||||
a.Run([]string{"run"}) |
||||
} |
@ -1,19 +0,0 @@ |
||||
package cli_test |
||||
|
||||
import ( |
||||
"reflect" |
||||
"testing" |
||||
) |
||||
|
||||
/* Test Helpers */ |
||||
func expect(t *testing.T, a interface{}, b interface{}) { |
||||
if a != b { |
||||
t.Errorf("Expected %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a)) |
||||
} |
||||
} |
||||
|
||||
func refute(t *testing.T, a interface{}, b interface{}) { |
||||
if a == b { |
||||
t.Errorf("Did not expect %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a)) |
||||
} |
||||
} |
@ -0,0 +1,629 @@ |
||||
// Copyright 2014 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/>.
|
||||
|
||||
// +build opencl
|
||||
|
||||
package ethash |
||||
|
||||
//#cgo LDFLAGS: -w
|
||||
//#include <stdint.h>
|
||||
//#include <string.h>
|
||||
//#include "src/libethash/internal.h"
|
||||
import "C" |
||||
|
||||
import ( |
||||
crand "crypto/rand" |
||||
"encoding/binary" |
||||
"fmt" |
||||
"math" |
||||
"math/big" |
||||
mrand "math/rand" |
||||
"strconv" |
||||
"strings" |
||||
"sync" |
||||
"sync/atomic" |
||||
"time" |
||||
"unsafe" |
||||
|
||||
"github.com/Gustav-Simonsson/go-opencl/cl" |
||||
"github.com/ethereum/go-ethereum/common" |
||||
"github.com/ethereum/go-ethereum/pow" |
||||
) |
||||
|
||||
/* |
||||
|
||||
This code have two main entry points: |
||||
|
||||
1. The initCL(...) function configures one or more OpenCL device |
||||
(for now only GPU) and loads the Ethash DAG onto device memory |
||||
|
||||
2. The Search(...) function loads a Ethash nonce into device(s) memory and |
||||
executes the Ethash OpenCL kernel. |
||||
|
||||
Throughout the code, we refer to "host memory" and "device memory". |
||||
For most systems (e.g. regular PC GPU miner) the host memory is RAM and |
||||
device memory is the GPU global memory (e.g. GDDR5). |
||||
|
||||
References mentioned in code comments: |
||||
|
||||
1. https://github.com/ethereum/wiki/wiki/Ethash
|
||||
2. https://github.com/ethereum/cpp-ethereum/blob/develop/libethash-cl/ethash_cl_miner.cpp
|
||||
3. https://www.khronos.org/registry/cl/sdk/1.2/docs/man/xhtml/
|
||||
4. http://amd-dev.wpengine.netdna-cdn.com/wordpress/media/2013/12/AMD_OpenCL_Programming_User_Guide.pdf
|
||||
|
||||
*/ |
||||
|
||||
type OpenCLDevice struct { |
||||
deviceId int |
||||
device *cl.Device |
||||
openCL11 bool // OpenCL version 1.1 and 1.2 are handled a bit different
|
||||
openCL12 bool |
||||
|
||||
dagBuf *cl.MemObject // Ethash full DAG in device mem
|
||||
headerBuf *cl.MemObject // Hash of block-to-mine in device mem
|
||||
searchBuffers []*cl.MemObject |
||||
|
||||
searchKernel *cl.Kernel |
||||
hashKernel *cl.Kernel |
||||
|
||||
queue *cl.CommandQueue |
||||
ctx *cl.Context |
||||
workGroupSize int |
||||
|
||||
nonceRand *mrand.Rand // seeded by crypto/rand, see comments where it's initialised
|
||||
result common.Hash |
||||
} |
||||
|
||||
type OpenCLMiner struct { |
||||
mu sync.Mutex |
||||
|
||||
ethash *Ethash // Ethash full DAG & cache in host mem
|
||||
|
||||
deviceIds []int |
||||
devices []*OpenCLDevice |
||||
|
||||
dagSize uint64 |
||||
|
||||
hashRate int32 // Go atomics & uint64 have some issues; int32 is supported on all platforms
|
||||
} |
||||
|
||||
type pendingSearch struct { |
||||
bufIndex uint32 |
||||
startNonce uint64 |
||||
} |
||||
|
||||
const ( |
||||
SIZEOF_UINT32 = 4 |
||||
|
||||
// See [1]
|
||||
ethashMixBytesLen = 128 |
||||
ethashAccesses = 64 |
||||
|
||||
// See [4]
|
||||
workGroupSize = 32 // must be multiple of 8
|
||||
maxSearchResults = 63 |
||||
searchBufSize = 2 |
||||
globalWorkSize = 1024 * 256 |
||||
) |
||||
|
||||
func NewCL(deviceIds []int) *OpenCLMiner { |
||||
ids := make([]int, len(deviceIds)) |
||||
copy(ids, deviceIds) |
||||
return &OpenCLMiner{ |
||||
ethash: New(), |
||||
dagSize: 0, // to see if we need to update DAG.
|
||||
deviceIds: ids, |
||||
} |
||||
} |
||||
|
||||
func PrintDevices() { |
||||
fmt.Println("=============================================") |
||||
fmt.Println("============ OpenCL Device Info =============") |
||||
fmt.Println("=============================================") |
||||
|
||||
var found []*cl.Device |
||||
|
||||
platforms, err := cl.GetPlatforms() |
||||
if err != nil { |
||||
fmt.Println("Plaform error (check your OpenCL installation): %v", err) |
||||
return |
||||
} |
||||
|
||||
for i, p := range platforms { |
||||
fmt.Println("Platform id ", i) |
||||
fmt.Println("Platform Name ", p.Name()) |
||||
fmt.Println("Platform Vendor ", p.Vendor()) |
||||
fmt.Println("Platform Version ", p.Version()) |
||||
fmt.Println("Platform Extensions ", p.Extensions()) |
||||
fmt.Println("Platform Profile ", p.Profile()) |
||||
fmt.Println("") |
||||
|
||||
devices, err := cl.GetDevices(p, cl.DeviceTypeGPU) |
||||
if err != nil { |
||||
fmt.Println("Device error (check your GPU drivers) :", err) |
||||
return |
||||
} |
||||
|
||||
for _, d := range devices { |
||||
fmt.Println("Device OpenCL id ", i) |
||||
fmt.Println("Device id for mining ", len(found)) |
||||
fmt.Println("Device Name ", d.Name()) |
||||
fmt.Println("Vendor ", d.Vendor()) |
||||
fmt.Println("Version ", d.Version()) |
||||
fmt.Println("Driver version ", d.DriverVersion()) |
||||
fmt.Println("Address bits ", d.AddressBits()) |
||||
fmt.Println("Max clock freq ", d.MaxClockFrequency()) |
||||
fmt.Println("Global mem size ", d.GlobalMemSize()) |
||||
fmt.Println("Max constant buffer size", d.MaxConstantBufferSize()) |
||||
fmt.Println("Max mem alloc size ", d.MaxMemAllocSize()) |
||||
fmt.Println("Max compute units ", d.MaxComputeUnits()) |
||||
fmt.Println("Max work group size ", d.MaxWorkGroupSize()) |
||||
fmt.Println("Max work item sizes ", d.MaxWorkItemSizes()) |
||||
fmt.Println("=============================================") |
||||
|
||||
found = append(found, d) |
||||
} |
||||
} |
||||
if len(found) == 0 { |
||||
fmt.Println("Found no GPU(s). Check that your OS can see the GPU(s)") |
||||
} else { |
||||
var idsFormat string |
||||
for i := 0; i < len(found); i++ { |
||||
idsFormat += strconv.Itoa(i) |
||||
if i != len(found)-1 { |
||||
idsFormat += "," |
||||
} |
||||
} |
||||
fmt.Printf("Found %v devices. Benchmark first GPU: geth gpubench 0\n", len(found)) |
||||
fmt.Printf("Mine using all GPUs: geth --minegpu %v\n", idsFormat) |
||||
} |
||||
} |
||||
|
||||
// See [2]. We basically do the same here, but the Go OpenCL bindings
|
||||
// are at a slightly higher abtraction level.
|
||||
func InitCL(blockNum uint64, c *OpenCLMiner) error { |
||||
platforms, err := cl.GetPlatforms() |
||||
if err != nil { |
||||
return fmt.Errorf("Plaform error: %v\nCheck your OpenCL installation and then run geth gpuinfo", err) |
||||
} |
||||
|
||||
var devices []*cl.Device |
||||
for _, p := range platforms { |
||||
ds, err := cl.GetDevices(p, cl.DeviceTypeGPU) |
||||
if err != nil { |
||||
return fmt.Errorf("Devices error: %v\nCheck your GPU drivers and then run geth gpuinfo", err) |
||||
} |
||||
for _, d := range ds { |
||||
devices = append(devices, d) |
||||
} |
||||
} |
||||
|
||||
pow := New() |
||||
_ = pow.getDAG(blockNum) // generates DAG if we don't have it
|
||||
pow.Light.getCache(blockNum) // and cache
|
||||
|
||||
c.ethash = pow |
||||
dagSize := uint64(C.ethash_get_datasize(C.uint64_t(blockNum))) |
||||
c.dagSize = dagSize |
||||
|
||||
for _, id := range c.deviceIds { |
||||
if id > len(devices)-1 { |
||||
return fmt.Errorf("Device id not found. See available device ids with: geth gpuinfo") |
||||
} else { |
||||
err := initCLDevice(id, devices[id], c) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
} |
||||
} |
||||
if len(c.devices) == 0 { |
||||
return fmt.Errorf("No GPU devices found") |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func initCLDevice(deviceId int, device *cl.Device, c *OpenCLMiner) error { |
||||
devMaxAlloc := uint64(device.MaxMemAllocSize()) |
||||
devGlobalMem := uint64(device.GlobalMemSize()) |
||||
|
||||
// TODO: more fine grained version logic
|
||||
if device.Version() == "OpenCL 1.0" { |
||||
fmt.Println("Device OpenCL version not supported: ", device.Version()) |
||||
return fmt.Errorf("opencl version not supported") |
||||
} |
||||
|
||||
var cl11, cl12 bool |
||||
if device.Version() == "OpenCL 1.1" { |
||||
cl11 = true |
||||
} |
||||
if device.Version() == "OpenCL 1.2" { |
||||
cl12 = true |
||||
} |
||||
|
||||
// log warnings but carry on; some device drivers report inaccurate values
|
||||
if c.dagSize > devGlobalMem { |
||||
fmt.Printf("WARNING: device memory may be insufficient: %v. DAG size: %v.\n", devGlobalMem, c.dagSize) |
||||
} |
||||
|
||||
if c.dagSize > devMaxAlloc { |
||||
fmt.Printf("WARNING: DAG size (%v) larger than device max memory allocation size (%v).\n", c.dagSize, devMaxAlloc) |
||||
fmt.Printf("You probably have to export GPU_MAX_ALLOC_PERCENT=95\n") |
||||
} |
||||
|
||||
fmt.Printf("Initialising device %v: %v\n", deviceId, device.Name()) |
||||
|
||||
context, err := cl.CreateContext([]*cl.Device{device}) |
||||
if err != nil { |
||||
return fmt.Errorf("failed creating context:", err) |
||||
} |
||||
|
||||
// TODO: test running with CL_QUEUE_PROFILING_ENABLE for profiling?
|
||||
queue, err := context.CreateCommandQueue(device, 0) |
||||
if err != nil { |
||||
return fmt.Errorf("command queue err:", err) |
||||
} |
||||
|
||||
// See [4] section 3.2 and [3] "clBuildProgram".
|
||||
// The OpenCL kernel code is compiled at run-time.
|
||||
kvs := make(map[string]string, 4) |
||||
kvs["GROUP_SIZE"] = strconv.FormatUint(workGroupSize, 10) |
||||
kvs["DAG_SIZE"] = strconv.FormatUint(c.dagSize/ethashMixBytesLen, 10) |
||||
kvs["ACCESSES"] = strconv.FormatUint(ethashAccesses, 10) |
||||
kvs["MAX_OUTPUTS"] = strconv.FormatUint(maxSearchResults, 10) |
||||
kernelCode := replaceWords(kernel, kvs) |
||||
|
||||
program, err := context.CreateProgramWithSource([]string{kernelCode}) |
||||
if err != nil { |
||||
return fmt.Errorf("program err:", err) |
||||
} |
||||
|
||||
/* if using AMD OpenCL impl, you can set this to debug on x86 CPU device. |
||||
see AMD OpenCL programming guide section 4.2 |
||||
|
||||
export in shell before running: |
||||
export AMD_OCL_BUILD_OPTIONS_APPEND="-g -O0" |
||||
export CPU_MAX_COMPUTE_UNITS=1 |
||||
|
||||
buildOpts := "-g -cl-opt-disable" |
||||
|
||||
*/ |
||||
buildOpts := "" |
||||
err = program.BuildProgram([]*cl.Device{device}, buildOpts) |
||||
if err != nil { |
||||
return fmt.Errorf("program build err:", err) |
||||
} |
||||
|
||||
var searchKernelName, hashKernelName string |
||||
searchKernelName = "ethash_search" |
||||
hashKernelName = "ethash_hash" |
||||
|
||||
searchKernel, err := program.CreateKernel(searchKernelName) |
||||
hashKernel, err := program.CreateKernel(hashKernelName) |
||||
if err != nil { |
||||
return fmt.Errorf("kernel err:", err) |
||||
} |
||||
|
||||
// TODO: when this DAG size appears, patch the Go bindings
|
||||
// (context.go) to work with uint64 as size_t
|
||||
if c.dagSize > math.MaxInt32 { |
||||
fmt.Println("DAG too large for allocation.") |
||||
return fmt.Errorf("DAG too large for alloc") |
||||
} |
||||
|
||||
// TODO: patch up Go bindings to work with size_t, will overflow if > maxint32
|
||||
// TODO: fuck. shit's gonna overflow around 2017-06-09 12:17:02
|
||||
dagBuf := *(new(*cl.MemObject)) |
||||
dagBuf, err = context.CreateEmptyBuffer(cl.MemReadOnly, int(c.dagSize)) |
||||
if err != nil { |
||||
return fmt.Errorf("allocating dag buf failed: ", err) |
||||
} |
||||
|
||||
// write DAG to device mem
|
||||
dagPtr := unsafe.Pointer(c.ethash.Full.current.ptr.data) |
||||
_, err = queue.EnqueueWriteBuffer(dagBuf, true, 0, int(c.dagSize), dagPtr, nil) |
||||
if err != nil { |
||||
return fmt.Errorf("writing to dag buf failed: ", err) |
||||
} |
||||
|
||||
searchBuffers := make([]*cl.MemObject, searchBufSize) |
||||
for i := 0; i < searchBufSize; i++ { |
||||
searchBuff, err := context.CreateEmptyBuffer(cl.MemWriteOnly, (1+maxSearchResults)*SIZEOF_UINT32) |
||||
if err != nil { |
||||
return fmt.Errorf("search buffer err:", err) |
||||
} |
||||
searchBuffers[i] = searchBuff |
||||
} |
||||
|
||||
headerBuf, err := context.CreateEmptyBuffer(cl.MemReadOnly, 32) |
||||
if err != nil { |
||||
return fmt.Errorf("header buffer err:", err) |
||||
} |
||||
|
||||
// Unique, random nonces are crucial for mining efficieny.
|
||||
// While we do not need cryptographically secure PRNG for nonces,
|
||||
// we want to have uniform distribution and minimal repetition of nonces.
|
||||
// We could guarantee strict uniqueness of nonces by generating unique ranges,
|
||||
// but a int64 seed from crypto/rand should be good enough.
|
||||
// we then use math/rand for speed and to avoid draining OS entropy pool
|
||||
seed, err := crand.Int(crand.Reader, big.NewInt(math.MaxInt64)) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
nonceRand := mrand.New(mrand.NewSource(seed.Int64())) |
||||
|
||||
deviceStruct := &OpenCLDevice{ |
||||
deviceId: deviceId, |
||||
device: device, |
||||
openCL11: cl11, |
||||
openCL12: cl12, |
||||
|
||||
dagBuf: dagBuf, |
||||
headerBuf: headerBuf, |
||||
searchBuffers: searchBuffers, |
||||
|
||||
searchKernel: searchKernel, |
||||
hashKernel: hashKernel, |
||||
|
||||
queue: queue, |
||||
ctx: context, |
||||
|
||||
workGroupSize: workGroupSize, |
||||
|
||||
nonceRand: nonceRand, |
||||
} |
||||
c.devices = append(c.devices, deviceStruct) |
||||
|
||||
return nil |
||||
} |
||||
|
||||
func (c *OpenCLMiner) Search(block pow.Block, stop <-chan struct{}, index int) (uint64, []byte) { |
||||
c.mu.Lock() |
||||
newDagSize := uint64(C.ethash_get_datasize(C.uint64_t(block.NumberU64()))) |
||||
if newDagSize > c.dagSize { |
||||
// TODO: clean up buffers from previous DAG?
|
||||
err := InitCL(block.NumberU64(), c) |
||||
if err != nil { |
||||
fmt.Println("OpenCL init error: ", err) |
||||
return 0, []byte{0} |
||||
} |
||||
} |
||||
defer c.mu.Unlock() |
||||
|
||||
// Avoid unneeded OpenCL initialisation if we received stop while running InitCL
|
||||
select { |
||||
case <-stop: |
||||
return 0, []byte{0} |
||||
default: |
||||
} |
||||
|
||||
headerHash := block.HashNoNonce() |
||||
diff := block.Difficulty() |
||||
target256 := new(big.Int).Div(maxUint256, diff) |
||||
target64 := new(big.Int).Rsh(target256, 192).Uint64() |
||||
var zero uint32 = 0 |
||||
|
||||
d := c.devices[index] |
||||
|
||||
_, err := d.queue.EnqueueWriteBuffer(d.headerBuf, false, 0, 32, unsafe.Pointer(&headerHash[0]), nil) |
||||
if err != nil { |
||||
fmt.Println("Error in Search clEnqueueWriterBuffer : ", err) |
||||
return 0, []byte{0} |
||||
} |
||||
|
||||
for i := 0; i < searchBufSize; i++ { |
||||
_, err := d.queue.EnqueueWriteBuffer(d.searchBuffers[i], false, 0, 4, unsafe.Pointer(&zero), nil) |
||||
if err != nil { |
||||
fmt.Println("Error in Search clEnqueueWriterBuffer : ", err) |
||||
return 0, []byte{0} |
||||
} |
||||
} |
||||
|
||||
// wait for all search buffers to complete
|
||||
err = d.queue.Finish() |
||||
if err != nil { |
||||
fmt.Println("Error in Search clFinish : ", err) |
||||
return 0, []byte{0} |
||||
} |
||||
|
||||
err = d.searchKernel.SetArg(1, d.headerBuf) |
||||
if err != nil { |
||||
fmt.Println("Error in Search clSetKernelArg : ", err) |
||||
return 0, []byte{0} |
||||
} |
||||
|
||||
err = d.searchKernel.SetArg(2, d.dagBuf) |
||||
if err != nil { |
||||
fmt.Println("Error in Search clSetKernelArg : ", err) |
||||
return 0, []byte{0} |
||||
} |
||||
|
||||
err = d.searchKernel.SetArg(4, target64) |
||||
if err != nil { |
||||
fmt.Println("Error in Search clSetKernelArg : ", err) |
||||
return 0, []byte{0} |
||||
} |
||||
err = d.searchKernel.SetArg(5, uint32(math.MaxUint32)) |
||||
if err != nil { |
||||
fmt.Println("Error in Search clSetKernelArg : ", err) |
||||
return 0, []byte{0} |
||||
} |
||||
|
||||
// wait on this before returning
|
||||
var preReturnEvent *cl.Event |
||||
if d.openCL12 { |
||||
preReturnEvent, err = d.ctx.CreateUserEvent() |
||||
if err != nil { |
||||
fmt.Println("Error in Search create CL user event : ", err) |
||||
return 0, []byte{0} |
||||
} |
||||
} |
||||
|
||||
pending := make([]pendingSearch, 0, searchBufSize) |
||||
var p *pendingSearch |
||||
searchBufIndex := uint32(0) |
||||
var checkNonce uint64 |
||||
loops := int64(0) |
||||
prevHashRate := int32(0) |
||||
start := time.Now().UnixNano() |
||||
// we grab a single random nonce and sets this as argument to the kernel search function
|
||||
// the device will then add each local threads gid to the nonce, creating a unique nonce
|
||||
// for each device computing unit executing in parallel
|
||||
initNonce := uint64(d.nonceRand.Int63()) |
||||
for nonce := initNonce; ; nonce += uint64(globalWorkSize) { |
||||
select { |
||||
case <-stop: |
||||
|
||||
/* |
||||
if d.openCL12 { |
||||
err = cl.WaitForEvents([]*cl.Event{preReturnEvent}) |
||||
if err != nil { |
||||
fmt.Println("Error in Search WaitForEvents: ", err) |
||||
} |
||||
} |
||||
*/ |
||||
|
||||
atomic.AddInt32(&c.hashRate, -prevHashRate) |
||||
return 0, []byte{0} |
||||
default: |
||||
} |
||||
|
||||
if (loops % (1 << 7)) == 0 { |
||||
elapsed := time.Now().UnixNano() - start |
||||
// TODO: verify if this is correct hash rate calculation
|
||||
hashes := (float64(1e9) / float64(elapsed)) * float64(loops*1024*256) |
||||
hashrateDiff := int32(hashes) - prevHashRate |
||||
prevHashRate = int32(hashes) |
||||
atomic.AddInt32(&c.hashRate, hashrateDiff) |
||||
} |
||||
loops++ |
||||
|
||||
err = d.searchKernel.SetArg(0, d.searchBuffers[searchBufIndex]) |
||||
if err != nil { |
||||
fmt.Println("Error in Search clSetKernelArg : ", err) |
||||
return 0, []byte{0} |
||||
} |
||||
err = d.searchKernel.SetArg(3, nonce) |
||||
if err != nil { |
||||
fmt.Println("Error in Search clSetKernelArg : ", err) |
||||
return 0, []byte{0} |
||||
} |
||||
|
||||
// execute kernel
|
||||
_, err := d.queue.EnqueueNDRangeKernel( |
||||
d.searchKernel, |
||||
[]int{0}, |
||||
[]int{globalWorkSize}, |
||||
[]int{d.workGroupSize}, |
||||
nil) |
||||
if err != nil { |
||||
fmt.Println("Error in Search clEnqueueNDRangeKernel : ", err) |
||||
return 0, []byte{0} |
||||
} |
||||
|
||||
pending = append(pending, pendingSearch{bufIndex: searchBufIndex, startNonce: nonce}) |
||||
searchBufIndex = (searchBufIndex + 1) % searchBufSize |
||||
|
||||
if len(pending) == searchBufSize { |
||||
p = &(pending[searchBufIndex]) |
||||
cres, _, err := d.queue.EnqueueMapBuffer(d.searchBuffers[p.bufIndex], true, |
||||
cl.MapFlagRead, 0, (1+maxSearchResults)*SIZEOF_UINT32, |
||||
nil) |
||||
if err != nil { |
||||
fmt.Println("Error in Search clEnqueueMapBuffer: ", err) |
||||
return 0, []byte{0} |
||||
} |
||||
|
||||
results := cres.ByteSlice() |
||||
nfound := binary.LittleEndian.Uint32(results) |
||||
nfound = uint32(math.Min(float64(nfound), float64(maxSearchResults))) |
||||
// OpenCL returns the offsets from the start nonce
|
||||
for i := uint32(0); i < nfound; i++ { |
||||
lo := (i + 1) * SIZEOF_UINT32 |
||||
hi := (i + 2) * SIZEOF_UINT32 |
||||
upperNonce := uint64(binary.LittleEndian.Uint32(results[lo:hi])) |
||||
checkNonce = p.startNonce + upperNonce |
||||
if checkNonce != 0 { |
||||
cn := C.uint64_t(checkNonce) |
||||
ds := C.uint64_t(c.dagSize) |
||||
// We verify that the nonce is indeed a solution by
|
||||
// executing the Ethash verification function (on the CPU).
|
||||
ret := C.ethash_light_compute_internal(c.ethash.Light.current.ptr, ds, hashToH256(headerHash), cn) |
||||
// TODO: return result first
|
||||
if ret.success && h256ToHash(ret.result).Big().Cmp(target256) <= 0 { |
||||
_, err = d.queue.EnqueueUnmapMemObject(d.searchBuffers[p.bufIndex], cres, nil) |
||||
if err != nil { |
||||
fmt.Println("Error in Search clEnqueueUnmapMemObject: ", err) |
||||
} |
||||
if d.openCL12 { |
||||
err = cl.WaitForEvents([]*cl.Event{preReturnEvent}) |
||||
if err != nil { |
||||
fmt.Println("Error in Search WaitForEvents: ", err) |
||||
} |
||||
} |
||||
return checkNonce, C.GoBytes(unsafe.Pointer(&ret.mix_hash), C.int(32)) |
||||
} |
||||
|
||||
_, err := d.queue.EnqueueWriteBuffer(d.searchBuffers[p.bufIndex], false, 0, 4, unsafe.Pointer(&zero), nil) |
||||
if err != nil { |
||||
fmt.Println("Error in Search cl: EnqueueWriteBuffer", err) |
||||
return 0, []byte{0} |
||||
} |
||||
} |
||||
} |
||||
_, err = d.queue.EnqueueUnmapMemObject(d.searchBuffers[p.bufIndex], cres, nil) |
||||
if err != nil { |
||||
fmt.Println("Error in Search clEnqueueUnMapMemObject: ", err) |
||||
return 0, []byte{0} |
||||
} |
||||
pending = append(pending[:searchBufIndex], pending[searchBufIndex+1:]...) |
||||
} |
||||
} |
||||
if d.openCL12 { |
||||
err := cl.WaitForEvents([]*cl.Event{preReturnEvent}) |
||||
if err != nil { |
||||
fmt.Println("Error in Search clWaitForEvents: ", err) |
||||
return 0, []byte{0} |
||||
} |
||||
} |
||||
return 0, []byte{0} |
||||
} |
||||
|
||||
func (c *OpenCLMiner) Verify(block pow.Block) bool { |
||||
return c.ethash.Light.Verify(block) |
||||
} |
||||
func (c *OpenCLMiner) GetHashrate() int64 { |
||||
return int64(atomic.LoadInt32(&c.hashRate)) |
||||
} |
||||
func (c *OpenCLMiner) Turbo(on bool) { |
||||
// This is GPU mining. Always be turbo.
|
||||
} |
||||
|
||||
func replaceWords(text string, kvs map[string]string) string { |
||||
for k, v := range kvs { |
||||
text = strings.Replace(text, k, v, -1) |
||||
} |
||||
return text |
||||
} |
||||
|
||||
func logErr(err error) { |
||||
if err != nil { |
||||
fmt.Println("Error in OpenCL call:", err) |
||||
} |
||||
} |
||||
|
||||
func argErr(err error) error { |
||||
return fmt.Errorf("arg err: %v", err) |
||||
} |
600
Godeps/_workspace/src/github.com/ethereum/ethash/ethash_opencl_kernel_go_str.go
generated
vendored
600
Godeps/_workspace/src/github.com/ethereum/ethash/ethash_opencl_kernel_go_str.go
generated
vendored
@ -0,0 +1,600 @@ |
||||
package ethash |
||||
|
||||
/* DO NOT EDIT!!! |
||||
|
||||
This code is version controlled at |
||||
https://github.com/ethereum/cpp-ethereum/blob/develop/libethash-cl/ethash_cl_miner_kernel.cl
|
||||
|
||||
If needed change it there first, then copy over here. |
||||
*/ |
||||
|
||||
const kernel = ` |
||||
// author Tim Hughes <tim@twistedfury.com>
|
||||
// Tested on Radeon HD 7850
|
||||
// Hashrate: 15940347 hashes/s
|
||||
// Bandwidth: 124533 MB/s
|
||||
// search kernel should fit in <= 84 VGPRS (3 wavefronts)
|
||||
|
||||
#define THREADS_PER_HASH (128 / 16) |
||||
#define HASHES_PER_LOOP (GROUP_SIZE / THREADS_PER_HASH) |
||||
|
||||
#define FNV_PRIME 0x01000193 |
||||
|
||||
__constant uint2 const Keccak_f1600_RC[24] = { |
||||
(uint2)(0x00000001, 0x00000000), |
||||
(uint2)(0x00008082, 0x00000000), |
||||
(uint2)(0x0000808a, 0x80000000), |
||||
(uint2)(0x80008000, 0x80000000), |
||||
(uint2)(0x0000808b, 0x00000000), |
||||
(uint2)(0x80000001, 0x00000000), |
||||
(uint2)(0x80008081, 0x80000000), |
||||
(uint2)(0x00008009, 0x80000000), |
||||
(uint2)(0x0000008a, 0x00000000), |
||||
(uint2)(0x00000088, 0x00000000), |
||||
(uint2)(0x80008009, 0x00000000), |
||||
(uint2)(0x8000000a, 0x00000000), |
||||
(uint2)(0x8000808b, 0x00000000), |
||||
(uint2)(0x0000008b, 0x80000000), |
||||
(uint2)(0x00008089, 0x80000000), |
||||
(uint2)(0x00008003, 0x80000000), |
||||
(uint2)(0x00008002, 0x80000000), |
||||
(uint2)(0x00000080, 0x80000000), |
||||
(uint2)(0x0000800a, 0x00000000), |
||||
(uint2)(0x8000000a, 0x80000000), |
||||
(uint2)(0x80008081, 0x80000000), |
||||
(uint2)(0x00008080, 0x80000000), |
||||
(uint2)(0x80000001, 0x00000000), |
||||
(uint2)(0x80008008, 0x80000000), |
||||
}; |
||||
|
||||
void keccak_f1600_round(uint2* a, uint r, uint out_size) |
||||
{ |
||||
#if !__ENDIAN_LITTLE__ |
||||
for (uint i = 0; i != 25; ++i) |
||||
a[i] = a[i].yx; |
||||
#endif |
||||
|
||||
uint2 b[25]; |
||||
uint2 t; |
||||
|
||||
// Theta
|
||||
b[0] = a[0] ^ a[5] ^ a[10] ^ a[15] ^ a[20]; |
||||
b[1] = a[1] ^ a[6] ^ a[11] ^ a[16] ^ a[21]; |
||||
b[2] = a[2] ^ a[7] ^ a[12] ^ a[17] ^ a[22]; |
||||
b[3] = a[3] ^ a[8] ^ a[13] ^ a[18] ^ a[23]; |
||||
b[4] = a[4] ^ a[9] ^ a[14] ^ a[19] ^ a[24]; |
||||
t = b[4] ^ (uint2)(b[1].x << 1 | b[1].y >> 31, b[1].y << 1 | b[1].x >> 31); |
||||
a[0] ^= t; |
||||
a[5] ^= t; |
||||
a[10] ^= t; |
||||
a[15] ^= t; |
||||
a[20] ^= t; |
||||
t = b[0] ^ (uint2)(b[2].x << 1 | b[2].y >> 31, b[2].y << 1 | b[2].x >> 31); |
||||
a[1] ^= t; |
||||
a[6] ^= t; |
||||
a[11] ^= t; |
||||
a[16] ^= t; |
||||
a[21] ^= t; |
||||
t = b[1] ^ (uint2)(b[3].x << 1 | b[3].y >> 31, b[3].y << 1 | b[3].x >> 31); |
||||
a[2] ^= t; |
||||
a[7] ^= t; |
||||
a[12] ^= t; |
||||
a[17] ^= t; |
||||
a[22] ^= t; |
||||
t = b[2] ^ (uint2)(b[4].x << 1 | b[4].y >> 31, b[4].y << 1 | b[4].x >> 31); |
||||
a[3] ^= t; |
||||
a[8] ^= t; |
||||
a[13] ^= t; |
||||
a[18] ^= t; |
||||
a[23] ^= t; |
||||
t = b[3] ^ (uint2)(b[0].x << 1 | b[0].y >> 31, b[0].y << 1 | b[0].x >> 31); |
||||
a[4] ^= t; |
||||
a[9] ^= t; |
||||
a[14] ^= t; |
||||
a[19] ^= t; |
||||
a[24] ^= t; |
||||
|
||||
// Rho Pi
|
||||
b[0] = a[0]; |
||||
b[10] = (uint2)(a[1].x << 1 | a[1].y >> 31, a[1].y << 1 | a[1].x >> 31); |
||||
b[7] = (uint2)(a[10].x << 3 | a[10].y >> 29, a[10].y << 3 | a[10].x >> 29); |
||||
b[11] = (uint2)(a[7].x << 6 | a[7].y >> 26, a[7].y << 6 | a[7].x >> 26); |
||||
b[17] = (uint2)(a[11].x << 10 | a[11].y >> 22, a[11].y << 10 | a[11].x >> 22); |
||||
b[18] = (uint2)(a[17].x << 15 | a[17].y >> 17, a[17].y << 15 | a[17].x >> 17); |
||||
b[3] = (uint2)(a[18].x << 21 | a[18].y >> 11, a[18].y << 21 | a[18].x >> 11); |
||||
b[5] = (uint2)(a[3].x << 28 | a[3].y >> 4, a[3].y << 28 | a[3].x >> 4); |
||||
b[16] = (uint2)(a[5].y << 4 | a[5].x >> 28, a[5].x << 4 | a[5].y >> 28); |
||||
b[8] = (uint2)(a[16].y << 13 | a[16].x >> 19, a[16].x << 13 | a[16].y >> 19); |
||||
b[21] = (uint2)(a[8].y << 23 | a[8].x >> 9, a[8].x << 23 | a[8].y >> 9); |
||||
b[24] = (uint2)(a[21].x << 2 | a[21].y >> 30, a[21].y << 2 | a[21].x >> 30); |
||||
b[4] = (uint2)(a[24].x << 14 | a[24].y >> 18, a[24].y << 14 | a[24].x >> 18); |
||||
b[15] = (uint2)(a[4].x << 27 | a[4].y >> 5, a[4].y << 27 | a[4].x >> 5); |
||||
b[23] = (uint2)(a[15].y << 9 | a[15].x >> 23, a[15].x << 9 | a[15].y >> 23); |
||||
b[19] = (uint2)(a[23].y << 24 | a[23].x >> 8, a[23].x << 24 | a[23].y >> 8); |
||||
b[13] = (uint2)(a[19].x << 8 | a[19].y >> 24, a[19].y << 8 | a[19].x >> 24); |
||||
b[12] = (uint2)(a[13].x << 25 | a[13].y >> 7, a[13].y << 25 | a[13].x >> 7); |
||||
b[2] = (uint2)(a[12].y << 11 | a[12].x >> 21, a[12].x << 11 | a[12].y >> 21); |
||||
b[20] = (uint2)(a[2].y << 30 | a[2].x >> 2, a[2].x << 30 | a[2].y >> 2); |
||||
b[14] = (uint2)(a[20].x << 18 | a[20].y >> 14, a[20].y << 18 | a[20].x >> 14); |
||||
b[22] = (uint2)(a[14].y << 7 | a[14].x >> 25, a[14].x << 7 | a[14].y >> 25); |
||||
b[9] = (uint2)(a[22].y << 29 | a[22].x >> 3, a[22].x << 29 | a[22].y >> 3); |
||||
b[6] = (uint2)(a[9].x << 20 | a[9].y >> 12, a[9].y << 20 | a[9].x >> 12); |
||||
b[1] = (uint2)(a[6].y << 12 | a[6].x >> 20, a[6].x << 12 | a[6].y >> 20); |
||||
|
||||
// Chi
|
||||
a[0] = bitselect(b[0] ^ b[2], b[0], b[1]); |
||||
a[1] = bitselect(b[1] ^ b[3], b[1], b[2]); |
||||
a[2] = bitselect(b[2] ^ b[4], b[2], b[3]); |
||||
a[3] = bitselect(b[3] ^ b[0], b[3], b[4]); |
||||
if (out_size >= 4) |
||||
{ |
||||
a[4] = bitselect(b[4] ^ b[1], b[4], b[0]); |
||||
a[5] = bitselect(b[5] ^ b[7], b[5], b[6]); |
||||
a[6] = bitselect(b[6] ^ b[8], b[6], b[7]); |
||||
a[7] = bitselect(b[7] ^ b[9], b[7], b[8]); |
||||
a[8] = bitselect(b[8] ^ b[5], b[8], b[9]); |
||||
if (out_size >= 8) |
||||
{ |
||||
a[9] = bitselect(b[9] ^ b[6], b[9], b[5]); |
||||
a[10] = bitselect(b[10] ^ b[12], b[10], b[11]); |
||||
a[11] = bitselect(b[11] ^ b[13], b[11], b[12]); |
||||
a[12] = bitselect(b[12] ^ b[14], b[12], b[13]); |
||||
a[13] = bitselect(b[13] ^ b[10], b[13], b[14]); |
||||
a[14] = bitselect(b[14] ^ b[11], b[14], b[10]); |
||||
a[15] = bitselect(b[15] ^ b[17], b[15], b[16]); |
||||
a[16] = bitselect(b[16] ^ b[18], b[16], b[17]); |
||||
a[17] = bitselect(b[17] ^ b[19], b[17], b[18]); |
||||
a[18] = bitselect(b[18] ^ b[15], b[18], b[19]); |
||||
a[19] = bitselect(b[19] ^ b[16], b[19], b[15]); |
||||
a[20] = bitselect(b[20] ^ b[22], b[20], b[21]); |
||||
a[21] = bitselect(b[21] ^ b[23], b[21], b[22]); |
||||
a[22] = bitselect(b[22] ^ b[24], b[22], b[23]); |
||||
a[23] = bitselect(b[23] ^ b[20], b[23], b[24]); |
||||
a[24] = bitselect(b[24] ^ b[21], b[24], b[20]); |
||||
} |
||||
} |
||||
|
||||
// Iota
|
||||
a[0] ^= Keccak_f1600_RC[r]; |
||||
|
||||
#if !__ENDIAN_LITTLE__ |
||||
for (uint i = 0; i != 25; ++i) |
||||
a[i] = a[i].yx; |
||||
#endif |
||||
} |
||||
|
||||
void keccak_f1600_no_absorb(ulong* a, uint in_size, uint out_size, uint isolate) |
||||
{ |
||||
for (uint i = in_size; i != 25; ++i) |
||||
{ |
||||
a[i] = 0; |
||||
} |
||||
#if __ENDIAN_LITTLE__ |
||||
a[in_size] ^= 0x0000000000000001; |
||||
a[24-out_size*2] ^= 0x8000000000000000; |
||||
#else |
||||
a[in_size] ^= 0x0100000000000000; |
||||
a[24-out_size*2] ^= 0x0000000000000080; |
||||
#endif |
||||
|
||||
// Originally I unrolled the first and last rounds to interface
|
||||
// better with surrounding code, however I haven't done this
|
||||
// without causing the AMD compiler to blow up the VGPR usage.
|
||||
uint r = 0; |
||||
do |
||||
{ |
||||
// This dynamic branch stops the AMD compiler unrolling the loop
|
||||
// and additionally saves about 33% of the VGPRs, enough to gain another
|
||||
// wavefront. Ideally we'd get 4 in flight, but 3 is the best I can
|
||||
// massage out of the compiler. It doesn't really seem to matter how
|
||||
// much we try and help the compiler save VGPRs because it seems to throw
|
||||
// that information away, hence the implementation of keccak here
|
||||
// doesn't bother.
|
||||
if (isolate) |
||||
{ |
||||
keccak_f1600_round((uint2*)a, r++, 25); |
||||
} |
||||
} |
||||
while (r < 23); |
||||
|
||||
// final round optimised for digest size
|
||||
keccak_f1600_round((uint2*)a, r++, out_size); |
||||
} |
||||
|
||||
#define copy(dst, src, count) for (uint i = 0; i != count; ++i) { (dst)[i] = (src)[i]; } |
||||
|
||||
#define countof(x) (sizeof(x) / sizeof(x[0])) |
||||
|
||||
uint fnv(uint x, uint y) |
||||
{ |
||||
return x * FNV_PRIME ^ y; |
||||
} |
||||
|
||||
uint4 fnv4(uint4 x, uint4 y) |
||||
{ |
||||
return x * FNV_PRIME ^ y; |
||||
} |
||||
|
||||
uint fnv_reduce(uint4 v) |
||||
{ |
||||
return fnv(fnv(fnv(v.x, v.y), v.z), v.w); |
||||
} |
||||
|
||||
typedef union |
||||
{ |
||||
ulong ulongs[32 / sizeof(ulong)]; |
||||
uint uints[32 / sizeof(uint)]; |
||||
} hash32_t; |
||||
|
||||
typedef union |
||||
{ |
||||
ulong ulongs[64 / sizeof(ulong)]; |
||||
uint4 uint4s[64 / sizeof(uint4)]; |
||||
} hash64_t; |
||||
|
||||
typedef union |
||||
{ |
||||
uint uints[128 / sizeof(uint)]; |
||||
uint4 uint4s[128 / sizeof(uint4)]; |
||||
} hash128_t; |
||||
|
||||
hash64_t init_hash(__constant hash32_t const* header, ulong nonce, uint isolate) |
||||
{ |
||||
hash64_t init; |
||||
uint const init_size = countof(init.ulongs); |
||||
uint const hash_size = countof(header->ulongs); |
||||
|
||||
// sha3_512(header .. nonce)
|
||||
ulong state[25]; |
||||
copy(state, header->ulongs, hash_size); |
||||
state[hash_size] = nonce; |
||||
keccak_f1600_no_absorb(state, hash_size + 1, init_size, isolate); |
||||
|
||||
copy(init.ulongs, state, init_size); |
||||
return init; |
||||
} |
||||
|
||||
uint inner_loop_chunks(uint4 init, uint thread_id, __local uint* share, __global hash128_t const* g_dag, __global hash128_t const* g_dag1, __global hash128_t const* g_dag2, __global hash128_t const* g_dag3, uint isolate) |
||||
{ |
||||
uint4 mix = init; |
||||
|
||||
// share init0
|
||||
if (thread_id == 0) |
||||
*share = mix.x; |
||||
barrier(CLK_LOCAL_MEM_FENCE); |
||||
uint init0 = *share; |
||||
|
||||
uint a = 0; |
||||
do |
||||
{ |
||||
bool update_share = thread_id == (a/4) % THREADS_PER_HASH; |
||||
|
||||
#pragma unroll |
||||
for (uint i = 0; i != 4; ++i) |
||||
{ |
||||
if (update_share) |
||||
{ |
||||
uint m[4] = { mix.x, mix.y, mix.z, mix.w }; |
||||
*share = fnv(init0 ^ (a+i), m[i]) % DAG_SIZE; |
||||
} |
||||
barrier(CLK_LOCAL_MEM_FENCE); |
||||
|
||||
mix = fnv4(mix, *share>=3 * DAG_SIZE / 4 ? g_dag3[*share - 3 * DAG_SIZE / 4].uint4s[thread_id] : *share>=DAG_SIZE / 2 ? g_dag2[*share - DAG_SIZE / 2].uint4s[thread_id] : *share>=DAG_SIZE / 4 ? g_dag1[*share - DAG_SIZE / 4].uint4s[thread_id]:g_dag[*share].uint4s[thread_id]); |
||||
} |
||||
} while ((a += 4) != (ACCESSES & isolate)); |
||||
|
||||
return fnv_reduce(mix); |
||||
} |
||||
|
||||
|
||||
|
||||
uint inner_loop(uint4 init, uint thread_id, __local uint* share, __global hash128_t const* g_dag, uint isolate) |
||||
{ |
||||
uint4 mix = init; |
||||
|
||||
// share init0
|
||||
if (thread_id == 0) |
||||
*share = mix.x; |
||||
barrier(CLK_LOCAL_MEM_FENCE); |
||||
uint init0 = *share; |
||||
|
||||
uint a = 0; |
||||
do |
||||
{ |
||||
bool update_share = thread_id == (a/4) % THREADS_PER_HASH; |
||||
|
||||
#pragma unroll |
||||
for (uint i = 0; i != 4; ++i) |
||||
{ |
||||
if (update_share) |
||||
{ |
||||
uint m[4] = { mix.x, mix.y, mix.z, mix.w }; |
||||
*share = fnv(init0 ^ (a+i), m[i]) % DAG_SIZE; |
||||
} |
||||
barrier(CLK_LOCAL_MEM_FENCE); |
||||
|
||||
mix = fnv4(mix, g_dag[*share].uint4s[thread_id]); |
||||
} |
||||
} |
||||
while ((a += 4) != (ACCESSES & isolate)); |
||||
|
||||
return fnv_reduce(mix); |
||||
} |
||||
|
||||
|
||||
hash32_t final_hash(hash64_t const* init, hash32_t const* mix, uint isolate) |
||||
{ |
||||
ulong state[25]; |
||||
|
||||
hash32_t hash; |
||||
uint const hash_size = countof(hash.ulongs); |
||||
uint const init_size = countof(init->ulongs); |
||||
uint const mix_size = countof(mix->ulongs); |
||||
|
||||
// keccak_256(keccak_512(header..nonce) .. mix);
|
||||
copy(state, init->ulongs, init_size); |
||||
copy(state + init_size, mix->ulongs, mix_size); |
||||
keccak_f1600_no_absorb(state, init_size+mix_size, hash_size, isolate); |
||||
|
||||
// copy out
|
||||
copy(hash.ulongs, state, hash_size); |
||||
return hash; |
||||
} |
||||
|
||||
hash32_t compute_hash_simple( |
||||
__constant hash32_t const* g_header, |
||||
__global hash128_t const* g_dag, |
||||
ulong nonce, |
||||
uint isolate |
||||
) |
||||
{ |
||||
hash64_t init = init_hash(g_header, nonce, isolate); |
||||
|
||||
hash128_t mix; |
||||
for (uint i = 0; i != countof(mix.uint4s); ++i) |
||||
{ |
||||
mix.uint4s[i] = init.uint4s[i % countof(init.uint4s)]; |
||||
} |
||||
|
||||
uint mix_val = mix.uints[0]; |
||||
uint init0 = mix.uints[0]; |
||||
uint a = 0; |
||||
do |
||||
{ |
||||
uint pi = fnv(init0 ^ a, mix_val) % DAG_SIZE; |
||||
uint n = (a+1) % countof(mix.uints); |
||||
|
||||
#pragma unroll |
||||
for (uint i = 0; i != countof(mix.uints); ++i) |
||||
{ |
||||
mix.uints[i] = fnv(mix.uints[i], g_dag[pi].uints[i]); |
||||
mix_val = i == n ? mix.uints[i] : mix_val; |
||||
} |
||||
} |
||||
while (++a != (ACCESSES & isolate)); |
||||
|
||||
// reduce to output
|
||||
hash32_t fnv_mix; |
||||
for (uint i = 0; i != countof(fnv_mix.uints); ++i) |
||||
{ |
||||
fnv_mix.uints[i] = fnv_reduce(mix.uint4s[i]); |
||||
} |
||||
|
||||
return final_hash(&init, &fnv_mix, isolate); |
||||
} |
||||
|
||||
typedef union |
||||
{ |
||||
struct |
||||
{ |
||||
hash64_t init; |
||||
uint pad; // avoid lds bank conflicts
|
||||
}; |
||||
hash32_t mix; |
||||
} compute_hash_share; |
||||
|
||||
|
||||
hash32_t compute_hash( |
||||
__local compute_hash_share* share, |
||||
__constant hash32_t const* g_header, |
||||
__global hash128_t const* g_dag, |
||||
ulong nonce, |
||||
uint isolate |
||||
) |
||||
{ |
||||
uint const gid = get_global_id(0); |
||||
|
||||
// Compute one init hash per work item.
|
||||
hash64_t init = init_hash(g_header, nonce, isolate); |
||||
|
||||
// Threads work together in this phase in groups of 8.
|
||||
uint const thread_id = gid % THREADS_PER_HASH; |
||||
uint const hash_id = (gid % GROUP_SIZE) / THREADS_PER_HASH; |
||||
|
||||
hash32_t mix; |
||||
uint i = 0; |
||||
do |
||||
{ |
||||
// share init with other threads
|
||||
if (i == thread_id) |
||||
share[hash_id].init = init; |
||||
barrier(CLK_LOCAL_MEM_FENCE); |
||||
|
||||
uint4 thread_init = share[hash_id].init.uint4s[thread_id % (64 / sizeof(uint4))]; |
||||
barrier(CLK_LOCAL_MEM_FENCE); |
||||
|
||||
uint thread_mix = inner_loop(thread_init, thread_id, share[hash_id].mix.uints, g_dag, isolate); |
||||
|
||||
share[hash_id].mix.uints[thread_id] = thread_mix; |
||||
barrier(CLK_LOCAL_MEM_FENCE); |
||||
|
||||
if (i == thread_id) |
||||
mix = share[hash_id].mix; |
||||
barrier(CLK_LOCAL_MEM_FENCE); |
||||
} |
||||
while (++i != (THREADS_PER_HASH & isolate)); |
||||
|
||||
return final_hash(&init, &mix, isolate); |
||||
} |
||||
|
||||
|
||||
hash32_t compute_hash_chunks( |
||||
__local compute_hash_share* share, |
||||
__constant hash32_t const* g_header, |
||||
__global hash128_t const* g_dag, |
||||
__global hash128_t const* g_dag1, |
||||
__global hash128_t const* g_dag2, |
||||
__global hash128_t const* g_dag3, |
||||
ulong nonce, |
||||
uint isolate |
||||
) |
||||
{ |
||||
uint const gid = get_global_id(0); |
||||
|
||||
// Compute one init hash per work item.
|
||||
hash64_t init = init_hash(g_header, nonce, isolate); |
||||
|
||||
// Threads work together in this phase in groups of 8.
|
||||
uint const thread_id = gid % THREADS_PER_HASH; |
||||
uint const hash_id = (gid % GROUP_SIZE) / THREADS_PER_HASH; |
||||
|
||||
hash32_t mix; |
||||
uint i = 0; |
||||
do |
||||
{ |
||||
// share init with other threads
|
||||
if (i == thread_id) |
||||
share[hash_id].init = init; |
||||
barrier(CLK_LOCAL_MEM_FENCE); |
||||
|
||||
uint4 thread_init = share[hash_id].init.uint4s[thread_id % (64 / sizeof(uint4))]; |
||||
barrier(CLK_LOCAL_MEM_FENCE); |
||||
|
||||
uint thread_mix = inner_loop_chunks(thread_init, thread_id, share[hash_id].mix.uints, g_dag, g_dag1, g_dag2, g_dag3, isolate); |
||||
|
||||
share[hash_id].mix.uints[thread_id] = thread_mix; |
||||
barrier(CLK_LOCAL_MEM_FENCE); |
||||
|
||||
if (i == thread_id) |
||||
mix = share[hash_id].mix; |
||||
barrier(CLK_LOCAL_MEM_FENCE); |
||||
} |
||||
while (++i != (THREADS_PER_HASH & isolate)); |
||||
|
||||
return final_hash(&init, &mix, isolate); |
||||
} |
||||
|
||||
__attribute__((reqd_work_group_size(GROUP_SIZE, 1, 1))) |
||||
__kernel void ethash_hash_simple( |
||||
__global hash32_t* g_hashes, |
||||
__constant hash32_t const* g_header, |
||||
__global hash128_t const* g_dag, |
||||
ulong start_nonce, |
||||
uint isolate |
||||
) |
||||
{ |
||||
uint const gid = get_global_id(0); |
||||
g_hashes[gid] = compute_hash_simple(g_header, g_dag, start_nonce + gid, isolate); |
||||
} |
||||
|
||||
__attribute__((reqd_work_group_size(GROUP_SIZE, 1, 1))) |
||||
__kernel void ethash_search_simple( |
||||
__global volatile uint* restrict g_output, |
||||
__constant hash32_t const* g_header, |
||||
__global hash128_t const* g_dag, |
||||
ulong start_nonce, |
||||
ulong target, |
||||
uint isolate |
||||
) |
||||
{ |
||||
uint const gid = get_global_id(0); |
||||
hash32_t hash = compute_hash_simple(g_header, g_dag, start_nonce + gid, isolate); |
||||
|
||||
if (hash.ulongs[countof(hash.ulongs)-1] < target) |
||||
{ |
||||
uint slot = min(convert_uint(MAX_OUTPUTS), convert_uint(atomic_inc(&g_output[0]) + 1)); |
||||
g_output[slot] = gid; |
||||
} |
||||
} |
||||
|
||||
|
||||
__attribute__((reqd_work_group_size(GROUP_SIZE, 1, 1))) |
||||
__kernel void ethash_hash( |
||||
__global hash32_t* g_hashes, |
||||
__constant hash32_t const* g_header, |
||||
__global hash128_t const* g_dag, |
||||
ulong start_nonce, |
||||
uint isolate |
||||
) |
||||
{ |
||||
__local compute_hash_share share[HASHES_PER_LOOP]; |
||||
|
||||
uint const gid = get_global_id(0); |
||||
g_hashes[gid] = compute_hash(share, g_header, g_dag, start_nonce + gid, isolate); |
||||
} |
||||
|
||||
__attribute__((reqd_work_group_size(GROUP_SIZE, 1, 1))) |
||||
__kernel void ethash_search( |
||||
__global volatile uint* restrict g_output, |
||||
__constant hash32_t const* g_header, |
||||
__global hash128_t const* g_dag, |
||||
ulong start_nonce, |
||||
ulong target, |
||||
uint isolate |
||||
) |
||||
{ |
||||
__local compute_hash_share share[HASHES_PER_LOOP]; |
||||
|
||||
uint const gid = get_global_id(0); |
||||
hash32_t hash = compute_hash(share, g_header, g_dag, start_nonce + gid, isolate); |
||||
|
||||
if (as_ulong(as_uchar8(hash.ulongs[0]).s76543210) < target) |
||||
{ |
||||
uint slot = min((uint)MAX_OUTPUTS, atomic_inc(&g_output[0]) + 1); |
||||
g_output[slot] = gid; |
||||
} |
||||
} |
||||
|
||||
__attribute__((reqd_work_group_size(GROUP_SIZE, 1, 1))) |
||||
__kernel void ethash_hash_chunks( |
||||
__global hash32_t* g_hashes, |
||||
__constant hash32_t const* g_header, |
||||
__global hash128_t const* g_dag, |
||||
__global hash128_t const* g_dag1, |
||||
__global hash128_t const* g_dag2, |
||||
__global hash128_t const* g_dag3, |
||||
ulong start_nonce, |
||||
uint isolate |
||||
) |
||||
{ |
||||
__local compute_hash_share share[HASHES_PER_LOOP]; |
||||
|
||||
uint const gid = get_global_id(0); |
||||
g_hashes[gid] = compute_hash_chunks(share, g_header, g_dag, g_dag1, g_dag2, g_dag3,start_nonce + gid, isolate); |
||||
} |
||||
|
||||
__attribute__((reqd_work_group_size(GROUP_SIZE, 1, 1))) |
||||
__kernel void ethash_search_chunks( |
||||
__global volatile uint* restrict g_output, |
||||
__constant hash32_t const* g_header, |
||||
__global hash128_t const* g_dag, |
||||
__global hash128_t const* g_dag1, |
||||
__global hash128_t const* g_dag2, |
||||
__global hash128_t const* g_dag3, |
||||
ulong start_nonce, |
||||
ulong target, |
||||
uint isolate |
||||
) |
||||
{ |
||||
__local compute_hash_share share[HASHES_PER_LOOP]; |
||||
|
||||
uint const gid = get_global_id(0); |
||||
hash32_t hash = compute_hash_chunks(share, g_header, g_dag, g_dag1, g_dag2, g_dag3, start_nonce + gid, isolate); |
||||
|
||||
if (as_ulong(as_uchar8(hash.ulongs[0]).s76543210) < target) |
||||
{ |
||||
uint slot = min(convert_uint(MAX_OUTPUTS), convert_uint(atomic_inc(&g_output[0]) + 1)); |
||||
g_output[slot] = gid; |
||||
} |
||||
} |
||||
` |
@ -0,0 +1 @@ |
||||
/gotasks/specs |
@ -0,0 +1,27 @@ |
||||
package main |
||||
|
||||
import ( |
||||
"log" |
||||
|
||||
"github.com/huin/goupnp/ssdp" |
||||
) |
||||
|
||||
func main() { |
||||
c := make(chan ssdp.Update) |
||||
srv, reg := ssdp.NewServerAndRegistry() |
||||
reg.AddListener(c) |
||||
go listener(c) |
||||
if err := srv.ListenAndServe(); err != nil { |
||||
log.Print("ListenAndServe failed: ", err) |
||||
} |
||||
} |
||||
|
||||
func listener(c <-chan ssdp.Update) { |
||||
for u := range c { |
||||
if u.Entry != nil { |
||||
log.Printf("Event: %v USN: %s Entry: %#v", u.EventType, u.USN, *u.Entry) |
||||
} else { |
||||
log.Printf("Event: %v USN: %s Entry: <nil>", u.EventType, u.USN) |
||||
} |
||||
} |
||||
} |
File diff suppressed because it is too large
Load Diff
940
Godeps/_workspace/src/github.com/huin/goupnp/dcps/internetgateway1/internetgateway1.go
generated
vendored
940
Godeps/_workspace/src/github.com/huin/goupnp/dcps/internetgateway1/internetgateway1.go
generated
vendored
File diff suppressed because it is too large
Load Diff
1733
Godeps/_workspace/src/github.com/huin/goupnp/dcps/internetgateway2/internetgateway2.go
generated
vendored
1733
Godeps/_workspace/src/github.com/huin/goupnp/dcps/internetgateway2/internetgateway2.go
generated
vendored
File diff suppressed because it is too large
Load Diff
@ -1,62 +0,0 @@ |
||||
package example_test |
||||
|
||||
import ( |
||||
"fmt" |
||||
"os" |
||||
|
||||
"github.com/huin/goupnp" |
||||
"github.com/huin/goupnp/dcps/internetgateway1" |
||||
) |
||||
|
||||
// Use discovered WANPPPConnection1 services to find external IP addresses.
|
||||
func Example_WANPPPConnection1_GetExternalIPAddress() { |
||||
clients, errors, err := internetgateway1.NewWANPPPConnection1Clients() |
||||
extIPClients := make([]GetExternalIPAddresser, len(clients)) |
||||
for i, client := range clients { |
||||
extIPClients[i] = client |
||||
} |
||||
DisplayExternalIPResults(extIPClients, errors, err) |
||||
// Output:
|
||||
} |
||||
|
||||
// Use discovered WANIPConnection services to find external IP addresses.
|
||||
func Example_WANIPConnection_GetExternalIPAddress() { |
||||
clients, errors, err := internetgateway1.NewWANIPConnection1Clients() |
||||
extIPClients := make([]GetExternalIPAddresser, len(clients)) |
||||
for i, client := range clients { |
||||
extIPClients[i] = client |
||||
} |
||||
DisplayExternalIPResults(extIPClients, errors, err) |
||||
// Output:
|
||||
} |
||||
|
||||
type GetExternalIPAddresser interface { |
||||
GetExternalIPAddress() (NewExternalIPAddress string, err error) |
||||
GetServiceClient() *goupnp.ServiceClient |
||||
} |
||||
|
||||
func DisplayExternalIPResults(clients []GetExternalIPAddresser, errors []error, err error) { |
||||
if err != nil { |
||||
fmt.Fprintln(os.Stderr, "Error discovering service with UPnP: ", err) |
||||
return |
||||
} |
||||
|
||||
if len(errors) > 0 { |
||||
fmt.Fprintf(os.Stderr, "Error discovering %d services:\n", len(errors)) |
||||
for _, err := range errors { |
||||
fmt.Println(" ", err) |
||||
} |
||||
} |
||||
|
||||
fmt.Fprintf(os.Stderr, "Successfully discovered %d services:\n", len(clients)) |
||||
for _, client := range clients { |
||||
device := &client.GetServiceClient().RootDevice.Device |
||||
|
||||
fmt.Fprintln(os.Stderr, " Device:", device.FriendlyName) |
||||
if addr, err := client.GetExternalIPAddress(); err != nil { |
||||
fmt.Fprintf(os.Stderr, " Failed to get external IP address: %v\n", err) |
||||
} else { |
||||
fmt.Fprintf(os.Stderr, " External IP address: %v\n", addr) |
||||
} |
||||
} |
||||
} |
@ -1,85 +0,0 @@ |
||||
package soap |
||||
|
||||
import ( |
||||
"bytes" |
||||
"io/ioutil" |
||||
"net/http" |
||||
"net/url" |
||||
"reflect" |
||||
"testing" |
||||
) |
||||
|
||||
type capturingRoundTripper struct { |
||||
err error |
||||
resp *http.Response |
||||
capturedReq *http.Request |
||||
} |
||||
|
||||
func (rt *capturingRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { |
||||
rt.capturedReq = req |
||||
return rt.resp, rt.err |
||||
} |
||||
|
||||
func TestActionInputs(t *testing.T) { |
||||
url, err := url.Parse("http://example.com/soap") |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
rt := &capturingRoundTripper{ |
||||
err: nil, |
||||
resp: &http.Response{ |
||||
StatusCode: 200, |
||||
Body: ioutil.NopCloser(bytes.NewBufferString(` |
||||
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"> |
||||
<s:Body> |
||||
<u:myactionResponse xmlns:u="mynamespace"> |
||||
<A>valueA</A> |
||||
<B>valueB</B> |
||||
</u:myactionResponse> |
||||
</s:Body> |
||||
</s:Envelope> |
||||
`)), |
||||
}, |
||||
} |
||||
client := SOAPClient{ |
||||
EndpointURL: *url, |
||||
HTTPClient: http.Client{ |
||||
Transport: rt, |
||||
}, |
||||
} |
||||
|
||||
type In struct { |
||||
Foo string |
||||
Bar string `soap:"bar"` |
||||
} |
||||
type Out struct { |
||||
A string |
||||
B string |
||||
} |
||||
in := In{"foo", "bar"} |
||||
gotOut := Out{} |
||||
err = client.PerformAction("mynamespace", "myaction", &in, &gotOut) |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
|
||||
wantBody := (soapPrefix + |
||||
`<u:myaction xmlns:u="mynamespace">` + |
||||
`<Foo>foo</Foo>` + |
||||
`<bar>bar</bar>` + |
||||
`</u:myaction>` + |
||||
soapSuffix) |
||||
body, err := ioutil.ReadAll(rt.capturedReq.Body) |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
gotBody := string(body) |
||||
if wantBody != gotBody { |
||||
t.Errorf("Bad request body\nwant: %q\n got: %q", wantBody, gotBody) |
||||
} |
||||
|
||||
wantOut := Out{"valueA", "valueB"} |
||||
if !reflect.DeepEqual(wantOut, gotOut) { |
||||
t.Errorf("Bad output\nwant: %+v\n got: %+v", wantOut, gotOut) |
||||
} |
||||
} |
@ -1,481 +0,0 @@ |
||||
package soap |
||||
|
||||
import ( |
||||
"bytes" |
||||
"math" |
||||
"testing" |
||||
"time" |
||||
) |
||||
|
||||
type convTest interface { |
||||
Marshal() (string, error) |
||||
Unmarshal(string) (interface{}, error) |
||||
Equal(result interface{}) bool |
||||
} |
||||
|
||||
// duper is an interface that convTest values may optionally also implement to
|
||||
// generate another convTest for a value in an otherwise identical testCase.
|
||||
type duper interface { |
||||
Dupe(tag string) []convTest |
||||
} |
||||
|
||||
type testCase struct { |
||||
value convTest |
||||
str string |
||||
wantMarshalErr bool |
||||
wantUnmarshalErr bool |
||||
noMarshal bool |
||||
noUnMarshal bool |
||||
tag string |
||||
} |
||||
|
||||
type Ui1Test uint8 |
||||
|
||||
func (v Ui1Test) Marshal() (string, error) { |
||||
return MarshalUi1(uint8(v)) |
||||
} |
||||
func (v Ui1Test) Unmarshal(s string) (interface{}, error) { |
||||
return UnmarshalUi1(s) |
||||
} |
||||
func (v Ui1Test) Equal(result interface{}) bool { |
||||
return uint8(v) == result.(uint8) |
||||
} |
||||
func (v Ui1Test) Dupe(tag string) []convTest { |
||||
if tag == "dupe" { |
||||
return []convTest{ |
||||
Ui2Test(v), |
||||
Ui4Test(v), |
||||
} |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
type Ui2Test uint16 |
||||
|
||||
func (v Ui2Test) Marshal() (string, error) { |
||||
return MarshalUi2(uint16(v)) |
||||
} |
||||
func (v Ui2Test) Unmarshal(s string) (interface{}, error) { |
||||
return UnmarshalUi2(s) |
||||
} |
||||
func (v Ui2Test) Equal(result interface{}) bool { |
||||
return uint16(v) == result.(uint16) |
||||
} |
||||
|
||||
type Ui4Test uint32 |
||||
|
||||
func (v Ui4Test) Marshal() (string, error) { |
||||
return MarshalUi4(uint32(v)) |
||||
} |
||||
func (v Ui4Test) Unmarshal(s string) (interface{}, error) { |
||||
return UnmarshalUi4(s) |
||||
} |
||||
func (v Ui4Test) Equal(result interface{}) bool { |
||||
return uint32(v) == result.(uint32) |
||||
} |
||||
|
||||
type I1Test int8 |
||||
|
||||
func (v I1Test) Marshal() (string, error) { |
||||
return MarshalI1(int8(v)) |
||||
} |
||||
func (v I1Test) Unmarshal(s string) (interface{}, error) { |
||||
return UnmarshalI1(s) |
||||
} |
||||
func (v I1Test) Equal(result interface{}) bool { |
||||
return int8(v) == result.(int8) |
||||
} |
||||
func (v I1Test) Dupe(tag string) []convTest { |
||||
if tag == "dupe" { |
||||
return []convTest{ |
||||
I2Test(v), |
||||
I4Test(v), |
||||
} |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
type I2Test int16 |
||||
|
||||
func (v I2Test) Marshal() (string, error) { |
||||
return MarshalI2(int16(v)) |
||||
} |
||||
func (v I2Test) Unmarshal(s string) (interface{}, error) { |
||||
return UnmarshalI2(s) |
||||
} |
||||
func (v I2Test) Equal(result interface{}) bool { |
||||
return int16(v) == result.(int16) |
||||
} |
||||
|
||||
type I4Test int32 |
||||
|
||||
func (v I4Test) Marshal() (string, error) { |
||||
return MarshalI4(int32(v)) |
||||
} |
||||
func (v I4Test) Unmarshal(s string) (interface{}, error) { |
||||
return UnmarshalI4(s) |
||||
} |
||||
func (v I4Test) Equal(result interface{}) bool { |
||||
return int32(v) == result.(int32) |
||||
} |
||||
|
||||
type IntTest int64 |
||||
|
||||
func (v IntTest) Marshal() (string, error) { |
||||
return MarshalInt(int64(v)) |
||||
} |
||||
func (v IntTest) Unmarshal(s string) (interface{}, error) { |
||||
return UnmarshalInt(s) |
||||
} |
||||
func (v IntTest) Equal(result interface{}) bool { |
||||
return int64(v) == result.(int64) |
||||
} |
||||
|
||||
type Fixed14_4Test float64 |
||||
|
||||
func (v Fixed14_4Test) Marshal() (string, error) { |
||||
return MarshalFixed14_4(float64(v)) |
||||
} |
||||
func (v Fixed14_4Test) Unmarshal(s string) (interface{}, error) { |
||||
return UnmarshalFixed14_4(s) |
||||
} |
||||
func (v Fixed14_4Test) Equal(result interface{}) bool { |
||||
return math.Abs(float64(v)-result.(float64)) < 0.001 |
||||
} |
||||
|
||||
type CharTest rune |
||||
|
||||
func (v CharTest) Marshal() (string, error) { |
||||
return MarshalChar(rune(v)) |
||||
} |
||||
func (v CharTest) Unmarshal(s string) (interface{}, error) { |
||||
return UnmarshalChar(s) |
||||
} |
||||
func (v CharTest) Equal(result interface{}) bool { |
||||
return rune(v) == result.(rune) |
||||
} |
||||
|
||||
type DateTest struct{ time.Time } |
||||
|
||||
func (v DateTest) Marshal() (string, error) { |
||||
return MarshalDate(time.Time(v.Time)) |
||||
} |
||||
func (v DateTest) Unmarshal(s string) (interface{}, error) { |
||||
return UnmarshalDate(s) |
||||
} |
||||
func (v DateTest) Equal(result interface{}) bool { |
||||
return v.Time.Equal(result.(time.Time)) |
||||
} |
||||
func (v DateTest) Dupe(tag string) []convTest { |
||||
if tag != "no:dateTime" { |
||||
return []convTest{DateTimeTest{v.Time}} |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
type TimeOfDayTest struct { |
||||
TimeOfDay |
||||
} |
||||
|
||||
func (v TimeOfDayTest) Marshal() (string, error) { |
||||
return MarshalTimeOfDay(v.TimeOfDay) |
||||
} |
||||
func (v TimeOfDayTest) Unmarshal(s string) (interface{}, error) { |
||||
return UnmarshalTimeOfDay(s) |
||||
} |
||||
func (v TimeOfDayTest) Equal(result interface{}) bool { |
||||
return v.TimeOfDay == result.(TimeOfDay) |
||||
} |
||||
func (v TimeOfDayTest) Dupe(tag string) []convTest { |
||||
if tag != "no:time.tz" { |
||||
return []convTest{TimeOfDayTzTest{v.TimeOfDay}} |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
type TimeOfDayTzTest struct { |
||||
TimeOfDay |
||||
} |
||||
|
||||
func (v TimeOfDayTzTest) Marshal() (string, error) { |
||||
return MarshalTimeOfDayTz(v.TimeOfDay) |
||||
} |
||||
func (v TimeOfDayTzTest) Unmarshal(s string) (interface{}, error) { |
||||
return UnmarshalTimeOfDayTz(s) |
||||
} |
||||
func (v TimeOfDayTzTest) Equal(result interface{}) bool { |
||||
return v.TimeOfDay == result.(TimeOfDay) |
||||
} |
||||
|
||||
type DateTimeTest struct{ time.Time } |
||||
|
||||
func (v DateTimeTest) Marshal() (string, error) { |
||||
return MarshalDateTime(time.Time(v.Time)) |
||||
} |
||||
func (v DateTimeTest) Unmarshal(s string) (interface{}, error) { |
||||
return UnmarshalDateTime(s) |
||||
} |
||||
func (v DateTimeTest) Equal(result interface{}) bool { |
||||
return v.Time.Equal(result.(time.Time)) |
||||
} |
||||
func (v DateTimeTest) Dupe(tag string) []convTest { |
||||
if tag != "no:dateTime.tz" { |
||||
return []convTest{DateTimeTzTest{v.Time}} |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
type DateTimeTzTest struct{ time.Time } |
||||
|
||||
func (v DateTimeTzTest) Marshal() (string, error) { |
||||
return MarshalDateTimeTz(time.Time(v.Time)) |
||||
} |
||||
func (v DateTimeTzTest) Unmarshal(s string) (interface{}, error) { |
||||
return UnmarshalDateTimeTz(s) |
||||
} |
||||
func (v DateTimeTzTest) Equal(result interface{}) bool { |
||||
return v.Time.Equal(result.(time.Time)) |
||||
} |
||||
|
||||
type BooleanTest bool |
||||
|
||||
func (v BooleanTest) Marshal() (string, error) { |
||||
return MarshalBoolean(bool(v)) |
||||
} |
||||
func (v BooleanTest) Unmarshal(s string) (interface{}, error) { |
||||
return UnmarshalBoolean(s) |
||||
} |
||||
func (v BooleanTest) Equal(result interface{}) bool { |
||||
return bool(v) == result.(bool) |
||||
} |
||||
|
||||
type BinBase64Test []byte |
||||
|
||||
func (v BinBase64Test) Marshal() (string, error) { |
||||
return MarshalBinBase64([]byte(v)) |
||||
} |
||||
func (v BinBase64Test) Unmarshal(s string) (interface{}, error) { |
||||
return UnmarshalBinBase64(s) |
||||
} |
||||
func (v BinBase64Test) Equal(result interface{}) bool { |
||||
return bytes.Equal([]byte(v), result.([]byte)) |
||||
} |
||||
|
||||
type BinHexTest []byte |
||||
|
||||
func (v BinHexTest) Marshal() (string, error) { |
||||
return MarshalBinHex([]byte(v)) |
||||
} |
||||
func (v BinHexTest) Unmarshal(s string) (interface{}, error) { |
||||
return UnmarshalBinHex(s) |
||||
} |
||||
func (v BinHexTest) Equal(result interface{}) bool { |
||||
return bytes.Equal([]byte(v), result.([]byte)) |
||||
} |
||||
|
||||
func Test(t *testing.T) { |
||||
const time010203 time.Duration = (1*3600 + 2*60 + 3) * time.Second |
||||
const time0102 time.Duration = (1*3600 + 2*60) * time.Second |
||||
const time01 time.Duration = (1 * 3600) * time.Second |
||||
const time235959 time.Duration = (23*3600 + 59*60 + 59) * time.Second |
||||
|
||||
// Fake out the local time for the implementation.
|
||||
localLoc = time.FixedZone("Fake/Local", 6*3600) |
||||
defer func() { |
||||
localLoc = time.Local |
||||
}() |
||||
|
||||
tests := []testCase{ |
||||
// ui1
|
||||
{str: "", value: Ui1Test(0), wantUnmarshalErr: true, noMarshal: true, tag: "dupe"}, |
||||
{str: " ", value: Ui1Test(0), wantUnmarshalErr: true, noMarshal: true, tag: "dupe"}, |
||||
{str: "abc", value: Ui1Test(0), wantUnmarshalErr: true, noMarshal: true, tag: "dupe"}, |
||||
{str: "-1", value: Ui1Test(0), wantUnmarshalErr: true, noMarshal: true, tag: "dupe"}, |
||||
{str: "0", value: Ui1Test(0), tag: "dupe"}, |
||||
{str: "1", value: Ui1Test(1), tag: "dupe"}, |
||||
{str: "255", value: Ui1Test(255), tag: "dupe"}, |
||||
{str: "256", value: Ui1Test(0), wantUnmarshalErr: true, noMarshal: true}, |
||||
|
||||
// ui2
|
||||
{str: "65535", value: Ui2Test(65535)}, |
||||
{str: "65536", value: Ui2Test(0), wantUnmarshalErr: true, noMarshal: true}, |
||||
|
||||
// ui4
|
||||
{str: "4294967295", value: Ui4Test(4294967295)}, |
||||
{str: "4294967296", value: Ui4Test(0), wantUnmarshalErr: true, noMarshal: true}, |
||||
|
||||
// i1
|
||||
{str: "", value: I1Test(0), wantUnmarshalErr: true, noMarshal: true, tag: "dupe"}, |
||||
{str: " ", value: I1Test(0), wantUnmarshalErr: true, noMarshal: true, tag: "dupe"}, |
||||
{str: "abc", value: I1Test(0), wantUnmarshalErr: true, noMarshal: true, tag: "dupe"}, |
||||
{str: "0", value: I1Test(0), tag: "dupe"}, |
||||
{str: "-1", value: I1Test(-1), tag: "dupe"}, |
||||
{str: "127", value: I1Test(127), tag: "dupe"}, |
||||
{str: "-128", value: I1Test(-128), tag: "dupe"}, |
||||
{str: "128", value: I1Test(0), wantUnmarshalErr: true, noMarshal: true}, |
||||
{str: "-129", value: I1Test(0), wantUnmarshalErr: true, noMarshal: true}, |
||||
|
||||
// i2
|
||||
{str: "32767", value: I2Test(32767)}, |
||||
{str: "-32768", value: I2Test(-32768)}, |
||||
{str: "32768", value: I2Test(0), wantUnmarshalErr: true, noMarshal: true}, |
||||
{str: "-32769", value: I2Test(0), wantUnmarshalErr: true, noMarshal: true}, |
||||
|
||||
// i4
|
||||
{str: "2147483647", value: I4Test(2147483647)}, |
||||
{str: "-2147483648", value: I4Test(-2147483648)}, |
||||
{str: "2147483648", value: I4Test(0), wantUnmarshalErr: true, noMarshal: true}, |
||||
{str: "-2147483649", value: I4Test(0), wantUnmarshalErr: true, noMarshal: true}, |
||||
|
||||
// int
|
||||
{str: "9223372036854775807", value: IntTest(9223372036854775807)}, |
||||
{str: "-9223372036854775808", value: IntTest(-9223372036854775808)}, |
||||
{str: "9223372036854775808", value: IntTest(0), wantUnmarshalErr: true, noMarshal: true}, |
||||
{str: "-9223372036854775809", value: IntTest(0), wantUnmarshalErr: true, noMarshal: true}, |
||||
|
||||
// fixed.14.4
|
||||
{str: "0.0000", value: Fixed14_4Test(0)}, |
||||
{str: "1.0000", value: Fixed14_4Test(1)}, |
||||
{str: "1.2346", value: Fixed14_4Test(1.23456)}, |
||||
{str: "-1.0000", value: Fixed14_4Test(-1)}, |
||||
{str: "-1.2346", value: Fixed14_4Test(-1.23456)}, |
||||
{str: "10000000000000.0000", value: Fixed14_4Test(1e13)}, |
||||
{str: "100000000000000.0000", value: Fixed14_4Test(1e14), wantMarshalErr: true, wantUnmarshalErr: true}, |
||||
{str: "-10000000000000.0000", value: Fixed14_4Test(-1e13)}, |
||||
{str: "-100000000000000.0000", value: Fixed14_4Test(-1e14), wantMarshalErr: true, wantUnmarshalErr: true}, |
||||
|
||||
// char
|
||||
{str: "a", value: CharTest('a')}, |
||||
{str: "z", value: CharTest('z')}, |
||||
{str: "\u1234", value: CharTest(0x1234)}, |
||||
{str: "aa", value: CharTest(0), wantMarshalErr: true, wantUnmarshalErr: true}, |
||||
{str: "", value: CharTest(0), wantMarshalErr: true, wantUnmarshalErr: true}, |
||||
|
||||
// date
|
||||
{str: "2013-10-08", value: DateTest{time.Date(2013, 10, 8, 0, 0, 0, 0, localLoc)}, tag: "no:dateTime"}, |
||||
{str: "20131008", value: DateTest{time.Date(2013, 10, 8, 0, 0, 0, 0, localLoc)}, noMarshal: true, tag: "no:dateTime"}, |
||||
{str: "2013-10-08T10:30:50", value: DateTest{}, wantUnmarshalErr: true, noMarshal: true, tag: "no:dateTime"}, |
||||
{str: "2013-10-08T10:30:50Z", value: DateTest{}, wantUnmarshalErr: true, noMarshal: true, tag: "no:dateTime"}, |
||||
{str: "", value: DateTest{}, wantMarshalErr: true, wantUnmarshalErr: true, noMarshal: true}, |
||||
{str: "-1", value: DateTest{}, wantUnmarshalErr: true, noMarshal: true}, |
||||
|
||||
// time
|
||||
{str: "00:00:00", value: TimeOfDayTest{TimeOfDay{FromMidnight: 0}}}, |
||||
{str: "000000", value: TimeOfDayTest{TimeOfDay{FromMidnight: 0}}, noMarshal: true}, |
||||
{str: "24:00:00", value: TimeOfDayTest{TimeOfDay{FromMidnight: 24 * time.Hour}}, noMarshal: true}, // ISO8601 special case
|
||||
{str: "24:01:00", value: TimeOfDayTest{}, wantUnmarshalErr: true, noMarshal: true}, |
||||
{str: "24:00:01", value: TimeOfDayTest{}, wantUnmarshalErr: true, noMarshal: true}, |
||||
{str: "25:00:00", value: TimeOfDayTest{}, wantUnmarshalErr: true, noMarshal: true}, |
||||
{str: "00:60:00", value: TimeOfDayTest{}, wantUnmarshalErr: true, noMarshal: true}, |
||||
{str: "00:00:60", value: TimeOfDayTest{}, wantUnmarshalErr: true, noMarshal: true}, |
||||
{str: "01:02:03", value: TimeOfDayTest{TimeOfDay{FromMidnight: time010203}}}, |
||||
{str: "010203", value: TimeOfDayTest{TimeOfDay{FromMidnight: time010203}}, noMarshal: true}, |
||||
{str: "23:59:59", value: TimeOfDayTest{TimeOfDay{FromMidnight: time235959}}}, |
||||
{str: "235959", value: TimeOfDayTest{TimeOfDay{FromMidnight: time235959}}, noMarshal: true}, |
||||
{str: "01:02", value: TimeOfDayTest{TimeOfDay{FromMidnight: time0102}}, noMarshal: true}, |
||||
{str: "0102", value: TimeOfDayTest{TimeOfDay{FromMidnight: time0102}}, noMarshal: true}, |
||||
{str: "01", value: TimeOfDayTest{TimeOfDay{FromMidnight: time01}}, noMarshal: true}, |
||||
{str: "foo 01:02:03", value: TimeOfDayTest{}, wantUnmarshalErr: true, noMarshal: true, tag: "no:time.tz"}, |
||||
{str: "foo\n01:02:03", value: TimeOfDayTest{}, wantUnmarshalErr: true, noMarshal: true, tag: "no:time.tz"}, |
||||
{str: "01:02:03 foo", value: TimeOfDayTest{}, wantUnmarshalErr: true, noMarshal: true, tag: "no:time.tz"}, |
||||
{str: "01:02:03\nfoo", value: TimeOfDayTest{}, wantUnmarshalErr: true, noMarshal: true, tag: "no:time.tz"}, |
||||
{str: "01:02:03Z", value: TimeOfDayTest{}, wantUnmarshalErr: true, noMarshal: true, tag: "no:time.tz"}, |
||||
{str: "01:02:03+01", value: TimeOfDayTest{}, wantUnmarshalErr: true, noMarshal: true, tag: "no:time.tz"}, |
||||
{str: "01:02:03+01:23", value: TimeOfDayTest{}, wantUnmarshalErr: true, noMarshal: true, tag: "no:time.tz"}, |
||||
{str: "01:02:03+0123", value: TimeOfDayTest{}, wantUnmarshalErr: true, noMarshal: true, tag: "no:time.tz"}, |
||||
{str: "01:02:03-01", value: TimeOfDayTest{}, wantUnmarshalErr: true, noMarshal: true, tag: "no:time.tz"}, |
||||
{str: "01:02:03-01:23", value: TimeOfDayTest{}, wantUnmarshalErr: true, noMarshal: true, tag: "no:time.tz"}, |
||||
{str: "01:02:03-0123", value: TimeOfDayTest{}, wantUnmarshalErr: true, noMarshal: true, tag: "no:time.tz"}, |
||||
|
||||
// time.tz
|
||||
{str: "24:00:01", value: TimeOfDayTzTest{}, wantUnmarshalErr: true, noMarshal: true}, |
||||
{str: "01Z", value: TimeOfDayTzTest{TimeOfDay{time01, true, 0}}, noMarshal: true}, |
||||
{str: "01:02:03Z", value: TimeOfDayTzTest{TimeOfDay{time010203, true, 0}}}, |
||||
{str: "01+01", value: TimeOfDayTzTest{TimeOfDay{time01, true, 3600}}, noMarshal: true}, |
||||
{str: "01:02:03+01", value: TimeOfDayTzTest{TimeOfDay{time010203, true, 3600}}, noMarshal: true}, |
||||
{str: "01:02:03+01:23", value: TimeOfDayTzTest{TimeOfDay{time010203, true, 3600 + 23*60}}}, |
||||
{str: "01:02:03+0123", value: TimeOfDayTzTest{TimeOfDay{time010203, true, 3600 + 23*60}}, noMarshal: true}, |
||||
{str: "01:02:03-01", value: TimeOfDayTzTest{TimeOfDay{time010203, true, -3600}}, noMarshal: true}, |
||||
{str: "01:02:03-01:23", value: TimeOfDayTzTest{TimeOfDay{time010203, true, -(3600 + 23*60)}}}, |
||||
{str: "01:02:03-0123", value: TimeOfDayTzTest{TimeOfDay{time010203, true, -(3600 + 23*60)}}, noMarshal: true}, |
||||
|
||||
// dateTime
|
||||
{str: "2013-10-08T00:00:00", value: DateTimeTest{time.Date(2013, 10, 8, 0, 0, 0, 0, localLoc)}, tag: "no:dateTime.tz"}, |
||||
{str: "20131008", value: DateTimeTest{time.Date(2013, 10, 8, 0, 0, 0, 0, localLoc)}, noMarshal: true}, |
||||
{str: "2013-10-08T10:30:50", value: DateTimeTest{time.Date(2013, 10, 8, 10, 30, 50, 0, localLoc)}, tag: "no:dateTime.tz"}, |
||||
{str: "2013-10-08T10:30:50T", value: DateTimeTest{}, wantUnmarshalErr: true, noMarshal: true}, |
||||
{str: "2013-10-08T10:30:50+01", value: DateTimeTest{}, wantUnmarshalErr: true, noMarshal: true, tag: "no:dateTime.tz"}, |
||||
{str: "2013-10-08T10:30:50+01:23", value: DateTimeTest{}, wantUnmarshalErr: true, noMarshal: true, tag: "no:dateTime.tz"}, |
||||
{str: "2013-10-08T10:30:50+0123", value: DateTimeTest{}, wantUnmarshalErr: true, noMarshal: true, tag: "no:dateTime.tz"}, |
||||
{str: "2013-10-08T10:30:50-01", value: DateTimeTest{}, wantUnmarshalErr: true, noMarshal: true, tag: "no:dateTime.tz"}, |
||||
{str: "2013-10-08T10:30:50-01:23", value: DateTimeTest{}, wantUnmarshalErr: true, noMarshal: true, tag: "no:dateTime.tz"}, |
||||
{str: "2013-10-08T10:30:50-0123", value: DateTimeTest{}, wantUnmarshalErr: true, noMarshal: true, tag: "no:dateTime.tz"}, |
||||
|
||||
// dateTime.tz
|
||||
{str: "2013-10-08T10:30:50", value: DateTimeTzTest{time.Date(2013, 10, 8, 10, 30, 50, 0, localLoc)}, noMarshal: true}, |
||||
{str: "2013-10-08T10:30:50+01", value: DateTimeTzTest{time.Date(2013, 10, 8, 10, 30, 50, 0, time.FixedZone("+01:00", 3600))}, noMarshal: true}, |
||||
{str: "2013-10-08T10:30:50+01:23", value: DateTimeTzTest{time.Date(2013, 10, 8, 10, 30, 50, 0, time.FixedZone("+01:23", 3600+23*60))}}, |
||||
{str: "2013-10-08T10:30:50+0123", value: DateTimeTzTest{time.Date(2013, 10, 8, 10, 30, 50, 0, time.FixedZone("+01:23", 3600+23*60))}, noMarshal: true}, |
||||
{str: "2013-10-08T10:30:50-01", value: DateTimeTzTest{time.Date(2013, 10, 8, 10, 30, 50, 0, time.FixedZone("-01:00", -3600))}, noMarshal: true}, |
||||
{str: "2013-10-08T10:30:50-01:23", value: DateTimeTzTest{time.Date(2013, 10, 8, 10, 30, 50, 0, time.FixedZone("-01:23", -(3600+23*60)))}}, |
||||
{str: "2013-10-08T10:30:50-0123", value: DateTimeTzTest{time.Date(2013, 10, 8, 10, 30, 50, 0, time.FixedZone("-01:23", -(3600+23*60)))}, noMarshal: true}, |
||||
|
||||
// boolean
|
||||
{str: "0", value: BooleanTest(false)}, |
||||
{str: "1", value: BooleanTest(true)}, |
||||
{str: "false", value: BooleanTest(false), noMarshal: true}, |
||||
{str: "true", value: BooleanTest(true), noMarshal: true}, |
||||
{str: "no", value: BooleanTest(false), noMarshal: true}, |
||||
{str: "yes", value: BooleanTest(true), noMarshal: true}, |
||||
{str: "", value: BooleanTest(false), noMarshal: true, wantUnmarshalErr: true}, |
||||
{str: "other", value: BooleanTest(false), noMarshal: true, wantUnmarshalErr: true}, |
||||
{str: "2", value: BooleanTest(false), noMarshal: true, wantUnmarshalErr: true}, |
||||
{str: "-1", value: BooleanTest(false), noMarshal: true, wantUnmarshalErr: true}, |
||||
|
||||
// bin.base64
|
||||
{str: "", value: BinBase64Test{}}, |
||||
{str: "YQ==", value: BinBase64Test("a")}, |
||||
{str: "TG9uZ2VyIFN0cmluZy4=", value: BinBase64Test("Longer String.")}, |
||||
{str: "TG9uZ2VyIEFsaWduZWQu", value: BinBase64Test("Longer Aligned.")}, |
||||
|
||||
// bin.hex
|
||||
{str: "", value: BinHexTest{}}, |
||||
{str: "61", value: BinHexTest("a")}, |
||||
{str: "4c6f6e67657220537472696e672e", value: BinHexTest("Longer String.")}, |
||||
{str: "4C6F6E67657220537472696E672E", value: BinHexTest("Longer String."), noMarshal: true}, |
||||
} |
||||
|
||||
// Generate extra test cases from convTests that implement duper.
|
||||
var extras []testCase |
||||
for i := range tests { |
||||
if duper, ok := tests[i].value.(duper); ok { |
||||
dupes := duper.Dupe(tests[i].tag) |
||||
for _, duped := range dupes { |
||||
dupedCase := testCase(tests[i]) |
||||
dupedCase.value = duped |
||||
extras = append(extras, dupedCase) |
||||
} |
||||
} |
||||
} |
||||
tests = append(tests, extras...) |
||||
|
||||
for _, test := range tests { |
||||
if test.noMarshal { |
||||
} else if resultStr, err := test.value.Marshal(); err != nil && !test.wantMarshalErr { |
||||
t.Errorf("For %T marshal %v, want %q, got error: %v", test.value, test.value, test.str, err) |
||||
} else if err == nil && test.wantMarshalErr { |
||||
t.Errorf("For %T marshal %v, want error, got %q", test.value, test.value, resultStr) |
||||
} else if err == nil && resultStr != test.str { |
||||
t.Errorf("For %T marshal %v, want %q, got %q", test.value, test.value, test.str, resultStr) |
||||
} |
||||
|
||||
if test.noUnMarshal { |
||||
} else if resultValue, err := test.value.Unmarshal(test.str); err != nil && !test.wantUnmarshalErr { |
||||
t.Errorf("For %T unmarshal %q, want %v, got error: %v", test.value, test.str, test.value, err) |
||||
} else if err == nil && test.wantUnmarshalErr { |
||||
t.Errorf("For %T unmarshal %q, want error, got %v", test.value, test.str, resultValue) |
||||
} else if err == nil && !test.value.Equal(resultValue) { |
||||
t.Errorf("For %T unmarshal %q, want %v, got %v", test.value, test.str, test.value, resultValue) |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,212 @@ |
||||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum 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 General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Contains the geth command usage template and generator.
|
||||
|
||||
package main |
||||
|
||||
import ( |
||||
"io" |
||||
|
||||
"github.com/codegangsta/cli" |
||||
"github.com/ethereum/go-ethereum/cmd/utils" |
||||
) |
||||
|
||||
// AppHelpTemplate is the test template for the default, global app help topic.
|
||||
var AppHelpTemplate = `NAME: |
||||
{{.App.Name}} - {{.App.Usage}} |
||||
|
||||
USAGE: |
||||
{{.App.HelpName}} [options]{{if .App.Commands}} command [command options]{{end}} {{if .App.ArgsUsage}}{{.App.ArgsUsage}}{{else}}[arguments...]{{end}} |
||||
{{if .App.Version}} |
||||
VERSION: |
||||
{{.App.Version}} |
||||
{{end}}{{if len .App.Authors}} |
||||
AUTHOR(S): |
||||
{{range .App.Authors}}{{ . }}{{end}} |
||||
{{end}}{{if .App.Commands}} |
||||
COMMANDS: |
||||
{{range .App.Commands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}} |
||||
{{end}}{{end}}{{if .FlagGroups}} |
||||
{{range .FlagGroups}}{{.Name}} OPTIONS: |
||||
{{range .Flags}}{{.}} |
||||
{{end}} |
||||
{{end}}{{end}}{{if .App.Copyright }} |
||||
COPYRIGHT: |
||||
{{.App.Copyright}} |
||||
{{end}} |
||||
` |
||||
|
||||
// flagGroup is a collection of flags belonging to a single topic.
|
||||
type flagGroup struct { |
||||
Name string |
||||
Flags []cli.Flag |
||||
} |
||||
|
||||
// AppHelpFlagGroups is the application flags, grouped by functionality.
|
||||
var AppHelpFlagGroups = []flagGroup{ |
||||
{ |
||||
Name: "ETHEREUM", |
||||
Flags: []cli.Flag{ |
||||
utils.DataDirFlag, |
||||
utils.NetworkIdFlag, |
||||
utils.OlympicFlag, |
||||
utils.TestNetFlag, |
||||
utils.DevModeFlag, |
||||
utils.GenesisFileFlag, |
||||
utils.IdentityFlag, |
||||
utils.FastSyncFlag, |
||||
utils.CacheFlag, |
||||
utils.BlockchainVersionFlag, |
||||
}, |
||||
}, |
||||
{ |
||||
Name: "ACCOUNT", |
||||
Flags: []cli.Flag{ |
||||
utils.UnlockedAccountFlag, |
||||
utils.PasswordFileFlag, |
||||
}, |
||||
}, |
||||
{ |
||||
Name: "API AND CONSOLE", |
||||
Flags: []cli.Flag{ |
||||
utils.RPCEnabledFlag, |
||||
utils.RPCListenAddrFlag, |
||||
utils.RPCPortFlag, |
||||
utils.RpcApiFlag, |
||||
utils.IPCDisabledFlag, |
||||
utils.IPCApiFlag, |
||||
utils.IPCPathFlag, |
||||
utils.RPCCORSDomainFlag, |
||||
utils.JSpathFlag, |
||||
utils.ExecFlag, |
||||
}, |
||||
}, |
||||
{ |
||||
Name: "NETWORKING", |
||||
Flags: []cli.Flag{ |
||||
utils.BootnodesFlag, |
||||
utils.ListenPortFlag, |
||||
utils.MaxPeersFlag, |
||||
utils.MaxPendingPeersFlag, |
||||
utils.NATFlag, |
||||
utils.NoDiscoverFlag, |
||||
utils.NodeKeyFileFlag, |
||||
utils.NodeKeyHexFlag, |
||||
}, |
||||
}, |
||||
{ |
||||
Name: "MINER", |
||||
Flags: []cli.Flag{ |
||||
utils.MiningEnabledFlag, |
||||
utils.MinerThreadsFlag, |
||||
utils.MiningGPUFlag, |
||||
utils.AutoDAGFlag, |
||||
utils.EtherbaseFlag, |
||||
utils.GasPriceFlag, |
||||
utils.ExtraDataFlag, |
||||
}, |
||||
}, |
||||
{ |
||||
Name: "GAS PRICE ORACLE", |
||||
Flags: []cli.Flag{ |
||||
utils.GpoMinGasPriceFlag, |
||||
utils.GpoMaxGasPriceFlag, |
||||
utils.GpoFullBlockRatioFlag, |
||||
utils.GpobaseStepDownFlag, |
||||
utils.GpobaseStepUpFlag, |
||||
utils.GpobaseCorrectionFactorFlag, |
||||
}, |
||||
}, |
||||
{ |
||||
Name: "VIRTUAL MACHINE", |
||||
Flags: []cli.Flag{ |
||||
utils.VMDebugFlag, |
||||
utils.VMEnableJitFlag, |
||||
utils.VMForceJitFlag, |
||||
utils.VMJitCacheFlag, |
||||
}, |
||||
}, |
||||
{ |
||||
Name: "LOGGING AND DEBUGGING", |
||||
Flags: []cli.Flag{ |
||||
utils.VerbosityFlag, |
||||
utils.LogVModuleFlag, |
||||
utils.BacktraceAtFlag, |
||||
utils.LogFileFlag, |
||||
utils.PProfEanbledFlag, |
||||
utils.PProfPortFlag, |
||||
utils.MetricsEnabledFlag, |
||||
}, |
||||
}, |
||||
{ |
||||
Name: "EXPERIMENTAL", |
||||
Flags: []cli.Flag{ |
||||
utils.WhisperEnabledFlag, |
||||
utils.NatspecEnabledFlag, |
||||
}, |
||||
}, |
||||
{ |
||||
Name: "MISCELLANEOUS", |
||||
Flags: []cli.Flag{ |
||||
utils.SolcPathFlag, |
||||
}, |
||||
}, |
||||
} |
||||
|
||||
func init() { |
||||
// Override the default app help template
|
||||
cli.AppHelpTemplate = AppHelpTemplate |
||||
|
||||
// Define a one shot struct to pass to the usage template
|
||||
type helpData struct { |
||||
App interface{} |
||||
FlagGroups []flagGroup |
||||
} |
||||
// Override the default app help printer, but only for the global app help
|
||||
originalHelpPrinter := cli.HelpPrinter |
||||
cli.HelpPrinter = func(w io.Writer, tmpl string, data interface{}) { |
||||
if tmpl == AppHelpTemplate { |
||||
// Iterate over all the flags and add any uncategorized ones
|
||||
categorized := make(map[string]struct{}) |
||||
for _, group := range AppHelpFlagGroups { |
||||
for _, flag := range group.Flags { |
||||
categorized[flag.String()] = struct{}{} |
||||
} |
||||
} |
||||
uncategorized := []cli.Flag{} |
||||
for _, flag := range data.(*cli.App).Flags { |
||||
if _, ok := categorized[flag.String()]; !ok { |
||||
uncategorized = append(uncategorized, flag) |
||||
} |
||||
} |
||||
if len(uncategorized) > 0 { |
||||
// Append all ungategorized options to the misc group
|
||||
miscs := len(AppHelpFlagGroups[len(AppHelpFlagGroups)-1].Flags) |
||||
AppHelpFlagGroups[len(AppHelpFlagGroups)-1].Flags = append(AppHelpFlagGroups[len(AppHelpFlagGroups)-1].Flags, uncategorized...) |
||||
|
||||
// Make sure they are removed afterwards
|
||||
defer func() { |
||||
AppHelpFlagGroups[len(AppHelpFlagGroups)-1].Flags = AppHelpFlagGroups[len(AppHelpFlagGroups)-1].Flags[:miscs] |
||||
}() |
||||
} |
||||
// Render out custom usage screen
|
||||
originalHelpPrinter(w, tmpl, helpData{data, AppHelpFlagGroups}) |
||||
} else { |
||||
originalHelpPrinter(w, tmpl, data) |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,190 @@ |
||||
// Copyright 2015 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/>.
|
||||
|
||||
// Spec at https://github.com/ethereum/wiki/wiki/ICAP:-Inter-exchange-Client-Address-Protocol
|
||||
|
||||
package common |
||||
|
||||
import ( |
||||
"errors" |
||||
"math/big" |
||||
"strconv" |
||||
"strings" |
||||
) |
||||
|
||||
var ( |
||||
Base36Chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" |
||||
ICAPLengthError = errors.New("Invalid ICAP length") |
||||
ICAPEncodingError = errors.New("Invalid ICAP encoding") |
||||
ICAPChecksumError = errors.New("Invalid ICAP checksum") |
||||
ICAPCountryCodeError = errors.New("Invalid ICAP country code") |
||||
ICAPAssetIdentError = errors.New("Invalid ICAP asset identifier") |
||||
ICAPInstCodeError = errors.New("Invalid ICAP institution code") |
||||
ICAPClientIdentError = errors.New("Invalid ICAP client identifier") |
||||
) |
||||
|
||||
func ICAPToAddress(s string) (Address, error) { |
||||
switch len(s) { |
||||
case 35: // "XE" + 2 digit checksum + 31 base-36 chars of address
|
||||
return parseICAP(s) |
||||
case 34: // "XE" + 2 digit checksum + 30 base-36 chars of address
|
||||
return parseICAP(s) |
||||
case 20: // "XE" + 2 digit checksum + 3-char asset identifier +
|
||||
// 4-char institution identifier + 9-char institution client identifier
|
||||
return parseIndirectICAP(s) |
||||
default: |
||||
return Address{}, ICAPLengthError |
||||
} |
||||
} |
||||
|
||||
func parseICAP(s string) (Address, error) { |
||||
if !strings.HasPrefix(s, "XE") { |
||||
return Address{}, ICAPCountryCodeError |
||||
} |
||||
if err := validCheckSum(s); err != nil { |
||||
return Address{}, err |
||||
} |
||||
// checksum is ISO13616, Ethereum address is base-36
|
||||
bigAddr, _ := new(big.Int).SetString(s[4:], 36) |
||||
return BigToAddress(bigAddr), nil |
||||
} |
||||
|
||||
func parseIndirectICAP(s string) (Address, error) { |
||||
if !strings.HasPrefix(s, "XE") { |
||||
return Address{}, ICAPCountryCodeError |
||||
} |
||||
if s[4:7] != "ETH" { |
||||
return Address{}, ICAPAssetIdentError |
||||
} |
||||
if err := validCheckSum(s); err != nil { |
||||
return Address{}, err |
||||
} |
||||
// TODO: integrate with ICAP namereg
|
||||
return Address{}, errors.New("not implemented") |
||||
} |
||||
|
||||
func AddressToICAP(a Address) (string, error) { |
||||
enc := base36Encode(a.Big()) |
||||
// zero padd encoded address to Direct ICAP length if needed
|
||||
if len(enc) < 30 { |
||||
enc = join(strings.Repeat("0", 30-len(enc)), enc) |
||||
} |
||||
icap := join("XE", checkDigits(enc), enc) |
||||
return icap, nil |
||||
} |
||||
|
||||
// TODO: integrate with ICAP namereg when it's available
|
||||
func AddressToIndirectICAP(a Address, instCode string) (string, error) { |
||||
// return addressToIndirectICAP(a, instCode)
|
||||
return "", errors.New("not implemented") |
||||
} |
||||
|
||||
func addressToIndirectICAP(a Address, instCode string) (string, error) { |
||||
// TODO: add addressToClientIdent which grabs client ident from ICAP namereg
|
||||
//clientIdent := addressToClientIdent(a)
|
||||
clientIdent := "todo" |
||||
return clientIdentToIndirectICAP(instCode, clientIdent) |
||||
} |
||||
|
||||
func clientIdentToIndirectICAP(instCode, clientIdent string) (string, error) { |
||||
if len(instCode) != 4 || !validBase36(instCode) { |
||||
return "", ICAPInstCodeError |
||||
} |
||||
if len(clientIdent) != 9 || !validBase36(instCode) { |
||||
return "", ICAPClientIdentError |
||||
} |
||||
|
||||
// currently ETH is only valid asset identifier
|
||||
s := join("ETH", instCode, clientIdent) |
||||
return join("XE", checkDigits(s), s), nil |
||||
} |
||||
|
||||
// https://en.wikipedia.org/wiki/International_Bank_Account_Number#Validating_the_IBAN
|
||||
func validCheckSum(s string) error { |
||||
s = join(s[4:], s[:4]) |
||||
expanded, err := iso13616Expand(s) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
checkSumNum, _ := new(big.Int).SetString(expanded, 10) |
||||
if checkSumNum.Mod(checkSumNum, Big97).Cmp(Big1) != 0 { |
||||
return ICAPChecksumError |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func checkDigits(s string) string { |
||||
expanded, _ := iso13616Expand(strings.Join([]string{s, "XE00"}, "")) |
||||
num, _ := new(big.Int).SetString(expanded, 10) |
||||
num.Sub(Big98, num.Mod(num, Big97)) |
||||
|
||||
checkDigits := num.String() |
||||
// zero padd checksum
|
||||
if len(checkDigits) == 1 { |
||||
checkDigits = join("0", checkDigits) |
||||
} |
||||
return checkDigits |
||||
} |
||||
|
||||
// not base-36, but expansion to decimal literal: A = 10, B = 11, ... Z = 35
|
||||
func iso13616Expand(s string) (string, error) { |
||||
var parts []string |
||||
if !validBase36(s) { |
||||
return "", ICAPEncodingError |
||||
} |
||||
for _, c := range s { |
||||
i := uint64(c) |
||||
if i >= 65 { |
||||
parts = append(parts, strconv.FormatUint(uint64(c)-55, 10)) |
||||
} else { |
||||
parts = append(parts, string(c)) |
||||
} |
||||
} |
||||
return join(parts...), nil |
||||
} |
||||
|
||||
func base36Encode(i *big.Int) string { |
||||
var chars []rune |
||||
x := new(big.Int) |
||||
for { |
||||
x.Mod(i, Big36) |
||||
chars = append(chars, rune(Base36Chars[x.Uint64()])) |
||||
i.Div(i, Big36) |
||||
if i.Cmp(Big0) == 0 { |
||||
break |
||||
} |
||||
} |
||||
// reverse slice
|
||||
for i, j := 0, len(chars)-1; i < j; i, j = i+1, j-1 { |
||||
chars[i], chars[j] = chars[j], chars[i] |
||||
} |
||||
return string(chars) |
||||
} |
||||
|
||||
func validBase36(s string) bool { |
||||
for _, c := range s { |
||||
i := uint64(c) |
||||
// 0-9 or A-Z
|
||||
if i < 48 || (i > 57 && i < 65) || i > 90 { |
||||
return false |
||||
} |
||||
} |
||||
return true |
||||
} |
||||
|
||||
func join(s ...string) string { |
||||
return strings.Join(s, "") |
||||
} |
@ -0,0 +1,91 @@ |
||||
// Copyright 2015 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 common |
||||
|
||||
import "testing" |
||||
|
||||
/* More test vectors: |
||||
https://github.com/ethereum/web3.js/blob/master/test/iban.fromAddress.js
|
||||
https://github.com/ethereum/web3.js/blob/master/test/iban.toAddress.js
|
||||
https://github.com/ethereum/web3.js/blob/master/test/iban.isValid.js
|
||||
https://github.com/ethereum/libethereum/blob/develop/test/libethcore/icap.cpp
|
||||
*/ |
||||
|
||||
type icapTest struct { |
||||
name string |
||||
addr string |
||||
icap string |
||||
} |
||||
|
||||
var icapOKTests = []icapTest{ |
||||
{"Direct1", "0x52dc504a422f0e2a9e7632a34a50f1a82f8224c7", "XE499OG1EH8ZZI0KXC6N83EKGT1BM97P2O7"}, |
||||
{"Direct2", "0x11c5496aee77c1ba1f0854206a26dda82a81d6d8", "XE1222Q908LN1QBBU6XUQSO1OHWJIOS46OO"}, |
||||
{"DirectZeroPrefix", "0x00c5496aee77c1ba1f0854206a26dda82a81d6d8", "XE7338O073KYGTWWZN0F2WZ0R8PX5ZPPZS"}, |
||||
{"DirectDoubleZeroPrefix", "0x0000a5327eab78357cbf2ae8f3d49fd9d90c7d22", "XE0600DQK33XDTYUCRI0KYM5ELAKXDWWF6"}, |
||||
} |
||||
|
||||
var icapInvalidTests = []icapTest{ |
||||
{"DirectInvalidCheckSum", "", "XE7438O073KYGTWWZN0F2WZ0R8PX5ZPPZS"}, |
||||
{"DirectInvalidCountryCode", "", "XD7338O073KYGTWWZN0F2WZ0R8PX5ZPPZS"}, |
||||
{"DirectInvalidLength36", "", "XE499OG1EH8ZZI0KXC6N83EKGT1BM97P2O77"}, |
||||
{"DirectInvalidLength33", "", "XE499OG1EH8ZZI0KXC6N83EKGT1BM97P2"}, |
||||
|
||||
{"IndirectInvalidCheckSum", "", "XE35ETHXREGGOPHERSSS"}, |
||||
{"IndirectInvalidAssetIdentifier", "", "XE34ETHXREGGOPHERSSS"}, |
||||
{"IndirectInvalidLength19", "", "XE34ETHXREGGOPHERSS"}, |
||||
{"IndirectInvalidLength21", "", "XE34ETHXREGGOPHERSSSS"}, |
||||
} |
||||
|
||||
func TestICAPOK(t *testing.T) { |
||||
for _, test := range icapOKTests { |
||||
decodeEncodeTest(HexToAddress(test.addr), test.icap, t) |
||||
} |
||||
} |
||||
|
||||
func TestICAPInvalid(t *testing.T) { |
||||
for _, test := range icapInvalidTests { |
||||
failedDecodingTest(test.icap, t) |
||||
} |
||||
} |
||||
|
||||
func decodeEncodeTest(addr0 Address, icap0 string, t *testing.T) { |
||||
icap1, err := AddressToICAP(addr0) |
||||
if err != nil { |
||||
t.Errorf("ICAP encoding failed: %s", err) |
||||
} |
||||
if icap1 != icap0 { |
||||
t.Errorf("ICAP mismatch: have: %s want: %s", icap1, icap0) |
||||
} |
||||
|
||||
addr1, err := ICAPToAddress(icap0) |
||||
if err != nil { |
||||
t.Errorf("ICAP decoding failed: %s", err) |
||||
} |
||||
if addr1 != addr0 { |
||||
t.Errorf("Address mismatch: have: %x want: %x", addr1, addr0) |
||||
} |
||||
} |
||||
|
||||
func failedDecodingTest(icap string, t *testing.T) { |
||||
addr, err := ICAPToAddress(icap) |
||||
if err == nil { |
||||
t.Errorf("Expected ICAP decoding to fail.") |
||||
} |
||||
if addr != (Address{}) { |
||||
t.Errorf("Expected empty Address on failed ICAP decoding.") |
||||
} |
||||
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,954 @@ |
||||
// Copyright 2014 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 core |
||||
|
||||
import ( |
||||
"fmt" |
||||
"math/big" |
||||
"math/rand" |
||||
"os" |
||||
"path/filepath" |
||||
"runtime" |
||||
"strconv" |
||||
"testing" |
||||
|
||||
"github.com/ethereum/ethash" |
||||
"github.com/ethereum/go-ethereum/common" |
||||
"github.com/ethereum/go-ethereum/core/types" |
||||
"github.com/ethereum/go-ethereum/core/vm" |
||||
"github.com/ethereum/go-ethereum/crypto" |
||||
"github.com/ethereum/go-ethereum/ethdb" |
||||
"github.com/ethereum/go-ethereum/event" |
||||
"github.com/ethereum/go-ethereum/params" |
||||
"github.com/ethereum/go-ethereum/pow" |
||||
"github.com/ethereum/go-ethereum/rlp" |
||||
"github.com/hashicorp/golang-lru" |
||||
) |
||||
|
||||
func init() { |
||||
runtime.GOMAXPROCS(runtime.NumCPU()) |
||||
} |
||||
|
||||
func thePow() pow.PoW { |
||||
pow, _ := ethash.NewForTesting() |
||||
return pow |
||||
} |
||||
|
||||
func theBlockChain(db ethdb.Database, t *testing.T) *BlockChain { |
||||
var eventMux event.TypeMux |
||||
WriteTestNetGenesisBlock(db, 0) |
||||
blockchain, err := NewBlockChain(db, thePow(), &eventMux) |
||||
if err != nil { |
||||
t.Error("failed creating chainmanager:", err) |
||||
t.FailNow() |
||||
return nil |
||||
} |
||||
blockMan := NewBlockProcessor(db, nil, blockchain, &eventMux) |
||||
blockchain.SetProcessor(blockMan) |
||||
|
||||
return blockchain |
||||
} |
||||
|
||||
// Test fork of length N starting from block i
|
||||
func testFork(t *testing.T, processor *BlockProcessor, i, n int, full bool, comparator func(td1, td2 *big.Int)) { |
||||
// Copy old chain up to #i into a new db
|
||||
db, processor2, err := newCanonical(i, full) |
||||
if err != nil { |
||||
t.Fatal("could not make new canonical in testFork", err) |
||||
} |
||||
// Assert the chains have the same header/block at #i
|
||||
var hash1, hash2 common.Hash |
||||
if full { |
||||
hash1 = processor.bc.GetBlockByNumber(uint64(i)).Hash() |
||||
hash2 = processor2.bc.GetBlockByNumber(uint64(i)).Hash() |
||||
} else { |
||||
hash1 = processor.bc.GetHeaderByNumber(uint64(i)).Hash() |
||||
hash2 = processor2.bc.GetHeaderByNumber(uint64(i)).Hash() |
||||
} |
||||
if hash1 != hash2 { |
||||
t.Errorf("chain content mismatch at %d: have hash %v, want hash %v", i, hash2, hash1) |
||||
} |
||||
// Extend the newly created chain
|
||||
var ( |
||||
blockChainB []*types.Block |
||||
headerChainB []*types.Header |
||||
) |
||||
if full { |
||||
blockChainB = makeBlockChain(processor2.bc.CurrentBlock(), n, db, forkSeed) |
||||
if _, err := processor2.bc.InsertChain(blockChainB); err != nil { |
||||
t.Fatalf("failed to insert forking chain: %v", err) |
||||
} |
||||
} else { |
||||
headerChainB = makeHeaderChain(processor2.bc.CurrentHeader(), n, db, forkSeed) |
||||
if _, err := processor2.bc.InsertHeaderChain(headerChainB, 1); err != nil { |
||||
t.Fatalf("failed to insert forking chain: %v", err) |
||||
} |
||||
} |
||||
// Sanity check that the forked chain can be imported into the original
|
||||
var tdPre, tdPost *big.Int |
||||
|
||||
if full { |
||||
tdPre = processor.bc.GetTd(processor.bc.CurrentBlock().Hash()) |
||||
if err := testBlockChainImport(blockChainB, processor); err != nil { |
||||
t.Fatalf("failed to import forked block chain: %v", err) |
||||
} |
||||
tdPost = processor.bc.GetTd(blockChainB[len(blockChainB)-1].Hash()) |
||||
} else { |
||||
tdPre = processor.bc.GetTd(processor.bc.CurrentHeader().Hash()) |
||||
if err := testHeaderChainImport(headerChainB, processor); err != nil { |
||||
t.Fatalf("failed to import forked header chain: %v", err) |
||||
} |
||||
tdPost = processor.bc.GetTd(headerChainB[len(headerChainB)-1].Hash()) |
||||
} |
||||
// Compare the total difficulties of the chains
|
||||
comparator(tdPre, tdPost) |
||||
} |
||||
|
||||
func printChain(bc *BlockChain) { |
||||
for i := bc.CurrentBlock().Number().Uint64(); i > 0; i-- { |
||||
b := bc.GetBlockByNumber(uint64(i)) |
||||
fmt.Printf("\t%x %v\n", b.Hash(), b.Difficulty()) |
||||
} |
||||
} |
||||
|
||||
// testBlockChainImport tries to process a chain of blocks, writing them into
|
||||
// the database if successful.
|
||||
func testBlockChainImport(chain []*types.Block, processor *BlockProcessor) error { |
||||
for _, block := range chain { |
||||
// Try and process the block
|
||||
if _, _, err := processor.Process(block); err != nil { |
||||
if IsKnownBlockErr(err) { |
||||
continue |
||||
} |
||||
return err |
||||
} |
||||
// Manually insert the block into the database, but don't reorganize (allows subsequent testing)
|
||||
processor.bc.mu.Lock() |
||||
WriteTd(processor.chainDb, block.Hash(), new(big.Int).Add(block.Difficulty(), processor.bc.GetTd(block.ParentHash()))) |
||||
WriteBlock(processor.chainDb, block) |
||||
processor.bc.mu.Unlock() |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// testHeaderChainImport tries to process a chain of header, writing them into
|
||||
// the database if successful.
|
||||
func testHeaderChainImport(chain []*types.Header, processor *BlockProcessor) error { |
||||
for _, header := range chain { |
||||
// Try and validate the header
|
||||
if err := processor.ValidateHeader(header, false, false); err != nil { |
||||
return err |
||||
} |
||||
// Manually insert the header into the database, but don't reorganize (allows subsequent testing)
|
||||
processor.bc.mu.Lock() |
||||
WriteTd(processor.chainDb, header.Hash(), new(big.Int).Add(header.Difficulty, processor.bc.GetTd(header.ParentHash))) |
||||
WriteHeader(processor.chainDb, header) |
||||
processor.bc.mu.Unlock() |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func loadChain(fn string, t *testing.T) (types.Blocks, error) { |
||||
fh, err := os.OpenFile(filepath.Join("..", "_data", fn), os.O_RDONLY, os.ModePerm) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
defer fh.Close() |
||||
|
||||
var chain types.Blocks |
||||
if err := rlp.Decode(fh, &chain); err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
return chain, nil |
||||
} |
||||
|
||||
func insertChain(done chan bool, blockchain *BlockChain, chain types.Blocks, t *testing.T) { |
||||
_, err := blockchain.InsertChain(chain) |
||||
if err != nil { |
||||
fmt.Println(err) |
||||
t.FailNow() |
||||
} |
||||
done <- true |
||||
} |
||||
|
||||
func TestLastBlock(t *testing.T) { |
||||
db, _ := ethdb.NewMemDatabase() |
||||
|
||||
bchain := theBlockChain(db, t) |
||||
block := makeBlockChain(bchain.CurrentBlock(), 1, db, 0)[0] |
||||
bchain.insert(block) |
||||
if block.Hash() != GetHeadBlockHash(db) { |
||||
t.Errorf("Write/Get HeadBlockHash failed") |
||||
} |
||||
} |
||||
|
||||
// Tests that given a starting canonical chain of a given size, it can be extended
|
||||
// with various length chains.
|
||||
func TestExtendCanonicalHeaders(t *testing.T) { testExtendCanonical(t, false) } |
||||
func TestExtendCanonicalBlocks(t *testing.T) { testExtendCanonical(t, true) } |
||||
|
||||
func testExtendCanonical(t *testing.T, full bool) { |
||||
length := 5 |
||||
|
||||
// Make first chain starting from genesis
|
||||
_, processor, err := newCanonical(length, full) |
||||
if err != nil { |
||||
t.Fatalf("failed to make new canonical chain: %v", err) |
||||
} |
||||
// Define the difficulty comparator
|
||||
better := func(td1, td2 *big.Int) { |
||||
if td2.Cmp(td1) <= 0 { |
||||
t.Errorf("total difficulty mismatch: have %v, expected more than %v", td2, td1) |
||||
} |
||||
} |
||||
// Start fork from current height
|
||||
testFork(t, processor, length, 1, full, better) |
||||
testFork(t, processor, length, 2, full, better) |
||||
testFork(t, processor, length, 5, full, better) |
||||
testFork(t, processor, length, 10, full, better) |
||||
} |
||||
|
||||
// Tests that given a starting canonical chain of a given size, creating shorter
|
||||
// forks do not take canonical ownership.
|
||||
func TestShorterForkHeaders(t *testing.T) { testShorterFork(t, false) } |
||||
func TestShorterForkBlocks(t *testing.T) { testShorterFork(t, true) } |
||||
|
||||
func testShorterFork(t *testing.T, full bool) { |
||||
length := 10 |
||||
|
||||
// Make first chain starting from genesis
|
||||
_, processor, err := newCanonical(length, full) |
||||
if err != nil { |
||||
t.Fatalf("failed to make new canonical chain: %v", err) |
||||
} |
||||
// Define the difficulty comparator
|
||||
worse := func(td1, td2 *big.Int) { |
||||
if td2.Cmp(td1) >= 0 { |
||||
t.Errorf("total difficulty mismatch: have %v, expected less than %v", td2, td1) |
||||
} |
||||
} |
||||
// Sum of numbers must be less than `length` for this to be a shorter fork
|
||||
testFork(t, processor, 0, 3, full, worse) |
||||
testFork(t, processor, 0, 7, full, worse) |
||||
testFork(t, processor, 1, 1, full, worse) |
||||
testFork(t, processor, 1, 7, full, worse) |
||||
testFork(t, processor, 5, 3, full, worse) |
||||
testFork(t, processor, 5, 4, full, worse) |
||||
} |
||||
|
||||
// Tests that given a starting canonical chain of a given size, creating longer
|
||||
// forks do take canonical ownership.
|
||||
func TestLongerForkHeaders(t *testing.T) { testLongerFork(t, false) } |
||||
func TestLongerForkBlocks(t *testing.T) { testLongerFork(t, true) } |
||||
|
||||
func testLongerFork(t *testing.T, full bool) { |
||||
length := 10 |
||||
|
||||
// Make first chain starting from genesis
|
||||
_, processor, err := newCanonical(length, full) |
||||
if err != nil { |
||||
t.Fatalf("failed to make new canonical chain: %v", err) |
||||
} |
||||
// Define the difficulty comparator
|
||||
better := func(td1, td2 *big.Int) { |
||||
if td2.Cmp(td1) <= 0 { |
||||
t.Errorf("total difficulty mismatch: have %v, expected more than %v", td2, td1) |
||||
} |
||||
} |
||||
// Sum of numbers must be greater than `length` for this to be a longer fork
|
||||
testFork(t, processor, 0, 11, full, better) |
||||
testFork(t, processor, 0, 15, full, better) |
||||
testFork(t, processor, 1, 10, full, better) |
||||
testFork(t, processor, 1, 12, full, better) |
||||
testFork(t, processor, 5, 6, full, better) |
||||
testFork(t, processor, 5, 8, full, better) |
||||
} |
||||
|
||||
// Tests that given a starting canonical chain of a given size, creating equal
|
||||
// forks do take canonical ownership.
|
||||
func TestEqualForkHeaders(t *testing.T) { testEqualFork(t, false) } |
||||
func TestEqualForkBlocks(t *testing.T) { testEqualFork(t, true) } |
||||
|
||||
func testEqualFork(t *testing.T, full bool) { |
||||
length := 10 |
||||
|
||||
// Make first chain starting from genesis
|
||||
_, processor, err := newCanonical(length, full) |
||||
if err != nil { |
||||
t.Fatalf("failed to make new canonical chain: %v", err) |
||||
} |
||||
// Define the difficulty comparator
|
||||
equal := func(td1, td2 *big.Int) { |
||||
if td2.Cmp(td1) != 0 { |
||||
t.Errorf("total difficulty mismatch: have %v, want %v", td2, td1) |
||||
} |
||||
} |
||||
// Sum of numbers must be equal to `length` for this to be an equal fork
|
||||
testFork(t, processor, 0, 10, full, equal) |
||||
testFork(t, processor, 1, 9, full, equal) |
||||
testFork(t, processor, 2, 8, full, equal) |
||||
testFork(t, processor, 5, 5, full, equal) |
||||
testFork(t, processor, 6, 4, full, equal) |
||||
testFork(t, processor, 9, 1, full, equal) |
||||
} |
||||
|
||||
// Tests that chains missing links do not get accepted by the processor.
|
||||
func TestBrokenHeaderChain(t *testing.T) { testBrokenChain(t, false) } |
||||
func TestBrokenBlockChain(t *testing.T) { testBrokenChain(t, true) } |
||||
|
||||
func testBrokenChain(t *testing.T, full bool) { |
||||
// Make chain starting from genesis
|
||||
db, processor, err := newCanonical(10, full) |
||||
if err != nil { |
||||
t.Fatalf("failed to make new canonical chain: %v", err) |
||||
} |
||||
// Create a forked chain, and try to insert with a missing link
|
||||
if full { |
||||
chain := makeBlockChain(processor.bc.CurrentBlock(), 5, db, forkSeed)[1:] |
||||
if err := testBlockChainImport(chain, processor); err == nil { |
||||
t.Errorf("broken block chain not reported") |
||||
} |
||||
} else { |
||||
chain := makeHeaderChain(processor.bc.CurrentHeader(), 5, db, forkSeed)[1:] |
||||
if err := testHeaderChainImport(chain, processor); err == nil { |
||||
t.Errorf("broken header chain not reported") |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestChainInsertions(t *testing.T) { |
||||
t.Skip("Skipped: outdated test files") |
||||
|
||||
db, _ := ethdb.NewMemDatabase() |
||||
|
||||
chain1, err := loadChain("valid1", t) |
||||
if err != nil { |
||||
fmt.Println(err) |
||||
t.FailNow() |
||||
} |
||||
|
||||
chain2, err := loadChain("valid2", t) |
||||
if err != nil { |
||||
fmt.Println(err) |
||||
t.FailNow() |
||||
} |
||||
|
||||
blockchain := theBlockChain(db, t) |
||||
|
||||
const max = 2 |
||||
done := make(chan bool, max) |
||||
|
||||
go insertChain(done, blockchain, chain1, t) |
||||
go insertChain(done, blockchain, chain2, t) |
||||
|
||||
for i := 0; i < max; i++ { |
||||
<-done |
||||
} |
||||
|
||||
if chain2[len(chain2)-1].Hash() != blockchain.CurrentBlock().Hash() { |
||||
t.Error("chain2 is canonical and shouldn't be") |
||||
} |
||||
|
||||
if chain1[len(chain1)-1].Hash() != blockchain.CurrentBlock().Hash() { |
||||
t.Error("chain1 isn't canonical and should be") |
||||
} |
||||
} |
||||
|
||||
func TestChainMultipleInsertions(t *testing.T) { |
||||
t.Skip("Skipped: outdated test files") |
||||
|
||||
db, _ := ethdb.NewMemDatabase() |
||||
|
||||
const max = 4 |
||||
chains := make([]types.Blocks, max) |
||||
var longest int |
||||
for i := 0; i < max; i++ { |
||||
var err error |
||||
name := "valid" + strconv.Itoa(i+1) |
||||
chains[i], err = loadChain(name, t) |
||||
if len(chains[i]) >= len(chains[longest]) { |
||||
longest = i |
||||
} |
||||
fmt.Println("loaded", name, "with a length of", len(chains[i])) |
||||
if err != nil { |
||||
fmt.Println(err) |
||||
t.FailNow() |
||||
} |
||||
} |
||||
|
||||
blockchain := theBlockChain(db, t) |
||||
|
||||
done := make(chan bool, max) |
||||
for i, chain := range chains { |
||||
// XXX the go routine would otherwise reference the same (chain[3]) variable and fail
|
||||
i := i |
||||
chain := chain |
||||
go func() { |
||||
insertChain(done, blockchain, chain, t) |
||||
fmt.Println(i, "done") |
||||
}() |
||||
} |
||||
|
||||
for i := 0; i < max; i++ { |
||||
<-done |
||||
} |
||||
|
||||
if chains[longest][len(chains[longest])-1].Hash() != blockchain.CurrentBlock().Hash() { |
||||
t.Error("Invalid canonical chain") |
||||
} |
||||
} |
||||
|
||||
type bproc struct{} |
||||
|
||||
func (bproc) Process(*types.Block) (vm.Logs, types.Receipts, error) { return nil, nil, nil } |
||||
func (bproc) ValidateHeader(*types.Header, bool, bool) error { return nil } |
||||
func (bproc) ValidateHeaderWithParent(*types.Header, *types.Header, bool, bool) error { return nil } |
||||
|
||||
func makeHeaderChainWithDiff(genesis *types.Block, d []int, seed byte) []*types.Header { |
||||
blocks := makeBlockChainWithDiff(genesis, d, seed) |
||||
headers := make([]*types.Header, len(blocks)) |
||||
for i, block := range blocks { |
||||
headers[i] = block.Header() |
||||
} |
||||
return headers |
||||
} |
||||
|
||||
func makeBlockChainWithDiff(genesis *types.Block, d []int, seed byte) []*types.Block { |
||||
var chain []*types.Block |
||||
for i, difficulty := range d { |
||||
header := &types.Header{ |
||||
Coinbase: common.Address{seed}, |
||||
Number: big.NewInt(int64(i + 1)), |
||||
Difficulty: big.NewInt(int64(difficulty)), |
||||
UncleHash: types.EmptyUncleHash, |
||||
TxHash: types.EmptyRootHash, |
||||
ReceiptHash: types.EmptyRootHash, |
||||
} |
||||
if i == 0 { |
||||
header.ParentHash = genesis.Hash() |
||||
} else { |
||||
header.ParentHash = chain[i-1].Hash() |
||||
} |
||||
block := types.NewBlockWithHeader(header) |
||||
chain = append(chain, block) |
||||
} |
||||
return chain |
||||
} |
||||
|
||||
func chm(genesis *types.Block, db ethdb.Database) *BlockChain { |
||||
var eventMux event.TypeMux |
||||
bc := &BlockChain{chainDb: db, genesisBlock: genesis, eventMux: &eventMux, pow: FakePow{}, rand: rand.New(rand.NewSource(0))} |
||||
bc.headerCache, _ = lru.New(100) |
||||
bc.bodyCache, _ = lru.New(100) |
||||
bc.bodyRLPCache, _ = lru.New(100) |
||||
bc.tdCache, _ = lru.New(100) |
||||
bc.blockCache, _ = lru.New(100) |
||||
bc.futureBlocks, _ = lru.New(100) |
||||
bc.processor = bproc{} |
||||
bc.ResetWithGenesisBlock(genesis) |
||||
|
||||
return bc |
||||
} |
||||
|
||||
// Tests that reorganizing a long difficult chain after a short easy one
|
||||
// overwrites the canonical numbers and links in the database.
|
||||
func TestReorgLongHeaders(t *testing.T) { testReorgLong(t, false) } |
||||
func TestReorgLongBlocks(t *testing.T) { testReorgLong(t, true) } |
||||
|
||||
func testReorgLong(t *testing.T, full bool) { |
||||
testReorg(t, []int{1, 2, 4}, []int{1, 2, 3, 4}, 10, full) |
||||
} |
||||
|
||||
// Tests that reorganizing a short difficult chain after a long easy one
|
||||
// overwrites the canonical numbers and links in the database.
|
||||
func TestReorgShortHeaders(t *testing.T) { testReorgShort(t, false) } |
||||
func TestReorgShortBlocks(t *testing.T) { testReorgShort(t, true) } |
||||
|
||||
func testReorgShort(t *testing.T, full bool) { |
||||
testReorg(t, []int{1, 2, 3, 4}, []int{1, 10}, 11, full) |
||||
} |
||||
|
||||
func testReorg(t *testing.T, first, second []int, td int64, full bool) { |
||||
// Create a pristine block chain
|
||||
db, _ := ethdb.NewMemDatabase() |
||||
genesis, _ := WriteTestNetGenesisBlock(db, 0) |
||||
bc := chm(genesis, db) |
||||
|
||||
// Insert an easy and a difficult chain afterwards
|
||||
if full { |
||||
bc.InsertChain(makeBlockChainWithDiff(genesis, first, 11)) |
||||
bc.InsertChain(makeBlockChainWithDiff(genesis, second, 22)) |
||||
} else { |
||||
bc.InsertHeaderChain(makeHeaderChainWithDiff(genesis, first, 11), 1) |
||||
bc.InsertHeaderChain(makeHeaderChainWithDiff(genesis, second, 22), 1) |
||||
} |
||||
// Check that the chain is valid number and link wise
|
||||
if full { |
||||
prev := bc.CurrentBlock() |
||||
for block := bc.GetBlockByNumber(bc.CurrentBlock().NumberU64() - 1); block.NumberU64() != 0; prev, block = block, bc.GetBlockByNumber(block.NumberU64()-1) { |
||||
if prev.ParentHash() != block.Hash() { |
||||
t.Errorf("parent block hash mismatch: have %x, want %x", prev.ParentHash(), block.Hash()) |
||||
} |
||||
} |
||||
} else { |
||||
prev := bc.CurrentHeader() |
||||
for header := bc.GetHeaderByNumber(bc.CurrentHeader().Number.Uint64() - 1); header.Number.Uint64() != 0; prev, header = header, bc.GetHeaderByNumber(header.Number.Uint64()-1) { |
||||
if prev.ParentHash != header.Hash() { |
||||
t.Errorf("parent header hash mismatch: have %x, want %x", prev.ParentHash, header.Hash()) |
||||
} |
||||
} |
||||
} |
||||
// Make sure the chain total difficulty is the correct one
|
||||
want := new(big.Int).Add(genesis.Difficulty(), big.NewInt(td)) |
||||
if full { |
||||
if have := bc.GetTd(bc.CurrentBlock().Hash()); have.Cmp(want) != 0 { |
||||
t.Errorf("total difficulty mismatch: have %v, want %v", have, want) |
||||
} |
||||
} else { |
||||
if have := bc.GetTd(bc.CurrentHeader().Hash()); have.Cmp(want) != 0 { |
||||
t.Errorf("total difficulty mismatch: have %v, want %v", have, want) |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Tests that the insertion functions detect banned hashes.
|
||||
func TestBadHeaderHashes(t *testing.T) { testBadHashes(t, false) } |
||||
func TestBadBlockHashes(t *testing.T) { testBadHashes(t, true) } |
||||
|
||||
func testBadHashes(t *testing.T, full bool) { |
||||
// Create a pristine block chain
|
||||
db, _ := ethdb.NewMemDatabase() |
||||
genesis, _ := WriteTestNetGenesisBlock(db, 0) |
||||
bc := chm(genesis, db) |
||||
|
||||
// Create a chain, ban a hash and try to import
|
||||
var err error |
||||
if full { |
||||
blocks := makeBlockChainWithDiff(genesis, []int{1, 2, 4}, 10) |
||||
BadHashes[blocks[2].Header().Hash()] = true |
||||
_, err = bc.InsertChain(blocks) |
||||
} else { |
||||
headers := makeHeaderChainWithDiff(genesis, []int{1, 2, 4}, 10) |
||||
BadHashes[headers[2].Hash()] = true |
||||
_, err = bc.InsertHeaderChain(headers, 1) |
||||
} |
||||
if !IsBadHashError(err) { |
||||
t.Errorf("error mismatch: want: BadHashError, have: %v", err) |
||||
} |
||||
} |
||||
|
||||
// Tests that bad hashes are detected on boot, and the chan rolled back to a
|
||||
// good state prior to the bad hash.
|
||||
func TestReorgBadHeaderHashes(t *testing.T) { testReorgBadHashes(t, false) } |
||||
func TestReorgBadBlockHashes(t *testing.T) { testReorgBadHashes(t, true) } |
||||
|
||||
func testReorgBadHashes(t *testing.T, full bool) { |
||||
// Create a pristine block chain
|
||||
db, _ := ethdb.NewMemDatabase() |
||||
genesis, _ := WriteTestNetGenesisBlock(db, 0) |
||||
bc := chm(genesis, db) |
||||
|
||||
// Create a chain, import and ban aferwards
|
||||
headers := makeHeaderChainWithDiff(genesis, []int{1, 2, 3, 4}, 10) |
||||
blocks := makeBlockChainWithDiff(genesis, []int{1, 2, 3, 4}, 10) |
||||
|
||||
if full { |
||||
if _, err := bc.InsertChain(blocks); err != nil { |
||||
t.Fatalf("failed to import blocks: %v", err) |
||||
} |
||||
if bc.CurrentBlock().Hash() != blocks[3].Hash() { |
||||
t.Errorf("last block hash mismatch: have: %x, want %x", bc.CurrentBlock().Hash(), blocks[3].Header().Hash()) |
||||
} |
||||
BadHashes[blocks[3].Header().Hash()] = true |
||||
defer func() { delete(BadHashes, blocks[3].Header().Hash()) }() |
||||
} else { |
||||
if _, err := bc.InsertHeaderChain(headers, 1); err != nil { |
||||
t.Fatalf("failed to import headers: %v", err) |
||||
} |
||||
if bc.CurrentHeader().Hash() != headers[3].Hash() { |
||||
t.Errorf("last header hash mismatch: have: %x, want %x", bc.CurrentHeader().Hash(), headers[3].Hash()) |
||||
} |
||||
BadHashes[headers[3].Hash()] = true |
||||
defer func() { delete(BadHashes, headers[3].Hash()) }() |
||||
} |
||||
// Create a new chain manager and check it rolled back the state
|
||||
ncm, err := NewBlockChain(db, FakePow{}, new(event.TypeMux)) |
||||
if err != nil { |
||||
t.Fatalf("failed to create new chain manager: %v", err) |
||||
} |
||||
if full { |
||||
if ncm.CurrentBlock().Hash() != blocks[2].Header().Hash() { |
||||
t.Errorf("last block hash mismatch: have: %x, want %x", ncm.CurrentBlock().Hash(), blocks[2].Header().Hash()) |
||||
} |
||||
if blocks[2].Header().GasLimit.Cmp(ncm.GasLimit()) != 0 { |
||||
t.Errorf("last block gasLimit mismatch: have: %x, want %x", ncm.GasLimit(), blocks[2].Header().GasLimit) |
||||
} |
||||
} else { |
||||
if ncm.CurrentHeader().Hash() != headers[2].Hash() { |
||||
t.Errorf("last header hash mismatch: have: %x, want %x", ncm.CurrentHeader().Hash(), headers[2].Hash()) |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Tests chain insertions in the face of one entity containing an invalid nonce.
|
||||
func TestHeadersInsertNonceError(t *testing.T) { testInsertNonceError(t, false) } |
||||
func TestBlocksInsertNonceError(t *testing.T) { testInsertNonceError(t, true) } |
||||
|
||||
func testInsertNonceError(t *testing.T, full bool) { |
||||
for i := 1; i < 25 && !t.Failed(); i++ { |
||||
// Create a pristine chain and database
|
||||
db, processor, err := newCanonical(0, full) |
||||
if err != nil { |
||||
t.Fatalf("failed to create pristine chain: %v", err) |
||||
} |
||||
bc := processor.bc |
||||
|
||||
// Create and insert a chain with a failing nonce
|
||||
var ( |
||||
failAt int |
||||
failRes int |
||||
failNum uint64 |
||||
failHash common.Hash |
||||
) |
||||
if full { |
||||
blocks := makeBlockChain(processor.bc.CurrentBlock(), i, db, 0) |
||||
|
||||
failAt = rand.Int() % len(blocks) |
||||
failNum = blocks[failAt].NumberU64() |
||||
failHash = blocks[failAt].Hash() |
||||
|
||||
processor.bc.pow = failPow{failNum} |
||||
processor.Pow = failPow{failNum} |
||||
|
||||
failRes, err = processor.bc.InsertChain(blocks) |
||||
} else { |
||||
headers := makeHeaderChain(processor.bc.CurrentHeader(), i, db, 0) |
||||
|
||||
failAt = rand.Int() % len(headers) |
||||
failNum = headers[failAt].Number.Uint64() |
||||
failHash = headers[failAt].Hash() |
||||
|
||||
processor.bc.pow = failPow{failNum} |
||||
processor.Pow = failPow{failNum} |
||||
|
||||
failRes, err = processor.bc.InsertHeaderChain(headers, 1) |
||||
} |
||||
// Check that the returned error indicates the nonce failure.
|
||||
if failRes != failAt { |
||||
t.Errorf("test %d: failure index mismatch: have %d, want %d", i, failRes, failAt) |
||||
} |
||||
if !IsBlockNonceErr(err) { |
||||
t.Fatalf("test %d: error mismatch: have %v, want nonce error", i, err) |
||||
} |
||||
nerr := err.(*BlockNonceErr) |
||||
if nerr.Number.Uint64() != failNum { |
||||
t.Errorf("test %d: number mismatch: have %v, want %v", i, nerr.Number, failNum) |
||||
} |
||||
if nerr.Hash != failHash { |
||||
t.Errorf("test %d: hash mismatch: have %x, want %x", i, nerr.Hash[:4], failHash[:4]) |
||||
} |
||||
// Check that all no blocks after the failing block have been inserted.
|
||||
for j := 0; j < i-failAt; j++ { |
||||
if full { |
||||
if block := bc.GetBlockByNumber(failNum + uint64(j)); block != nil { |
||||
t.Errorf("test %d: invalid block in chain: %v", i, block) |
||||
} |
||||
} else { |
||||
if header := bc.GetHeaderByNumber(failNum + uint64(j)); header != nil { |
||||
t.Errorf("test %d: invalid header in chain: %v", i, header) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Tests that fast importing a block chain produces the same chain data as the
|
||||
// classical full block processing.
|
||||
func TestFastVsFullChains(t *testing.T) { |
||||
// Configure and generate a sample block chain
|
||||
var ( |
||||
gendb, _ = ethdb.NewMemDatabase() |
||||
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") |
||||
address = crypto.PubkeyToAddress(key.PublicKey) |
||||
funds = big.NewInt(1000000000) |
||||
genesis = GenesisBlockForTesting(gendb, address, funds) |
||||
) |
||||
blocks, receipts := GenerateChain(genesis, gendb, 1024, func(i int, block *BlockGen) { |
||||
block.SetCoinbase(common.Address{0x00}) |
||||
|
||||
// If the block number is multiple of 3, send a few bonus transactions to the miner
|
||||
if i%3 == 2 { |
||||
for j := 0; j < i%4+1; j++ { |
||||
tx, err := types.NewTransaction(block.TxNonce(address), common.Address{0x00}, big.NewInt(1000), params.TxGas, nil, nil).SignECDSA(key) |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
block.AddTx(tx) |
||||
} |
||||
} |
||||
// If the block number is a multiple of 5, add a few bonus uncles to the block
|
||||
if i%5 == 5 { |
||||
block.AddUncle(&types.Header{ParentHash: block.PrevBlock(i - 1).Hash(), Number: big.NewInt(int64(i - 1))}) |
||||
} |
||||
}) |
||||
// Import the chain as an archive node for the comparison baseline
|
||||
archiveDb, _ := ethdb.NewMemDatabase() |
||||
WriteGenesisBlockForTesting(archiveDb, GenesisAccount{address, funds}) |
||||
|
||||
archive, _ := NewBlockChain(archiveDb, FakePow{}, new(event.TypeMux)) |
||||
archive.SetProcessor(NewBlockProcessor(archiveDb, FakePow{}, archive, new(event.TypeMux))) |
||||
|
||||
if n, err := archive.InsertChain(blocks); err != nil { |
||||
t.Fatalf("failed to process block %d: %v", n, err) |
||||
} |
||||
// Fast import the chain as a non-archive node to test
|
||||
fastDb, _ := ethdb.NewMemDatabase() |
||||
WriteGenesisBlockForTesting(fastDb, GenesisAccount{address, funds}) |
||||
fast, _ := NewBlockChain(fastDb, FakePow{}, new(event.TypeMux)) |
||||
fast.SetProcessor(NewBlockProcessor(fastDb, FakePow{}, fast, new(event.TypeMux))) |
||||
|
||||
headers := make([]*types.Header, len(blocks)) |
||||
for i, block := range blocks { |
||||
headers[i] = block.Header() |
||||
} |
||||
if n, err := fast.InsertHeaderChain(headers, 1); err != nil { |
||||
t.Fatalf("failed to insert header %d: %v", n, err) |
||||
} |
||||
if n, err := fast.InsertReceiptChain(blocks, receipts); err != nil { |
||||
t.Fatalf("failed to insert receipt %d: %v", n, err) |
||||
} |
||||
// Iterate over all chain data components, and cross reference
|
||||
for i := 0; i < len(blocks); i++ { |
||||
num, hash := blocks[i].NumberU64(), blocks[i].Hash() |
||||
|
||||
if ftd, atd := fast.GetTd(hash), archive.GetTd(hash); ftd.Cmp(atd) != 0 { |
||||
t.Errorf("block #%d [%x]: td mismatch: have %v, want %v", num, hash, ftd, atd) |
||||
} |
||||
if fheader, aheader := fast.GetHeader(hash), archive.GetHeader(hash); fheader.Hash() != aheader.Hash() { |
||||
t.Errorf("block #%d [%x]: header mismatch: have %v, want %v", num, hash, fheader, aheader) |
||||
} |
||||
if fblock, ablock := fast.GetBlock(hash), archive.GetBlock(hash); fblock.Hash() != ablock.Hash() { |
||||
t.Errorf("block #%d [%x]: block mismatch: have %v, want %v", num, hash, fblock, ablock) |
||||
} else if types.DeriveSha(fblock.Transactions()) != types.DeriveSha(ablock.Transactions()) { |
||||
t.Errorf("block #%d [%x]: transactions mismatch: have %v, want %v", num, hash, fblock.Transactions(), ablock.Transactions()) |
||||
} else if types.CalcUncleHash(fblock.Uncles()) != types.CalcUncleHash(ablock.Uncles()) { |
||||
t.Errorf("block #%d [%x]: uncles mismatch: have %v, want %v", num, hash, fblock.Uncles(), ablock.Uncles()) |
||||
} |
||||
if freceipts, areceipts := GetBlockReceipts(fastDb, hash), GetBlockReceipts(archiveDb, hash); types.DeriveSha(freceipts) != types.DeriveSha(areceipts) { |
||||
t.Errorf("block #%d [%x]: receipts mismatch: have %v, want %v", num, hash, freceipts, areceipts) |
||||
} |
||||
} |
||||
// Check that the canonical chains are the same between the databases
|
||||
for i := 0; i < len(blocks)+1; i++ { |
||||
if fhash, ahash := GetCanonicalHash(fastDb, uint64(i)), GetCanonicalHash(archiveDb, uint64(i)); fhash != ahash { |
||||
t.Errorf("block #%d: canonical hash mismatch: have %v, want %v", i, fhash, ahash) |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Tests that various import methods move the chain head pointers to the correct
|
||||
// positions.
|
||||
func TestLightVsFastVsFullChainHeads(t *testing.T) { |
||||
// Configure and generate a sample block chain
|
||||
var ( |
||||
gendb, _ = ethdb.NewMemDatabase() |
||||
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") |
||||
address = crypto.PubkeyToAddress(key.PublicKey) |
||||
funds = big.NewInt(1000000000) |
||||
genesis = GenesisBlockForTesting(gendb, address, funds) |
||||
) |
||||
height := uint64(1024) |
||||
blocks, receipts := GenerateChain(genesis, gendb, int(height), nil) |
||||
|
||||
// Configure a subchain to roll back
|
||||
remove := []common.Hash{} |
||||
for _, block := range blocks[height/2:] { |
||||
remove = append(remove, block.Hash()) |
||||
} |
||||
// Create a small assertion method to check the three heads
|
||||
assert := func(t *testing.T, kind string, chain *BlockChain, header uint64, fast uint64, block uint64) { |
||||
if num := chain.CurrentBlock().NumberU64(); num != block { |
||||
t.Errorf("%s head block mismatch: have #%v, want #%v", kind, num, block) |
||||
} |
||||
if num := chain.CurrentFastBlock().NumberU64(); num != fast { |
||||
t.Errorf("%s head fast-block mismatch: have #%v, want #%v", kind, num, fast) |
||||
} |
||||
if num := chain.CurrentHeader().Number.Uint64(); num != header { |
||||
t.Errorf("%s head header mismatch: have #%v, want #%v", kind, num, header) |
||||
} |
||||
} |
||||
// Import the chain as an archive node and ensure all pointers are updated
|
||||
archiveDb, _ := ethdb.NewMemDatabase() |
||||
WriteGenesisBlockForTesting(archiveDb, GenesisAccount{address, funds}) |
||||
|
||||
archive, _ := NewBlockChain(archiveDb, FakePow{}, new(event.TypeMux)) |
||||
archive.SetProcessor(NewBlockProcessor(archiveDb, FakePow{}, archive, new(event.TypeMux))) |
||||
|
||||
if n, err := archive.InsertChain(blocks); err != nil { |
||||
t.Fatalf("failed to process block %d: %v", n, err) |
||||
} |
||||
assert(t, "archive", archive, height, height, height) |
||||
archive.Rollback(remove) |
||||
assert(t, "archive", archive, height/2, height/2, height/2) |
||||
|
||||
// Import the chain as a non-archive node and ensure all pointers are updated
|
||||
fastDb, _ := ethdb.NewMemDatabase() |
||||
WriteGenesisBlockForTesting(fastDb, GenesisAccount{address, funds}) |
||||
fast, _ := NewBlockChain(fastDb, FakePow{}, new(event.TypeMux)) |
||||
fast.SetProcessor(NewBlockProcessor(fastDb, FakePow{}, fast, new(event.TypeMux))) |
||||
|
||||
headers := make([]*types.Header, len(blocks)) |
||||
for i, block := range blocks { |
||||
headers[i] = block.Header() |
||||
} |
||||
if n, err := fast.InsertHeaderChain(headers, 1); err != nil { |
||||
t.Fatalf("failed to insert header %d: %v", n, err) |
||||
} |
||||
if n, err := fast.InsertReceiptChain(blocks, receipts); err != nil { |
||||
t.Fatalf("failed to insert receipt %d: %v", n, err) |
||||
} |
||||
assert(t, "fast", fast, height, height, 0) |
||||
fast.Rollback(remove) |
||||
assert(t, "fast", fast, height/2, height/2, 0) |
||||
|
||||
// Import the chain as a light node and ensure all pointers are updated
|
||||
lightDb, _ := ethdb.NewMemDatabase() |
||||
WriteGenesisBlockForTesting(lightDb, GenesisAccount{address, funds}) |
||||
light, _ := NewBlockChain(lightDb, FakePow{}, new(event.TypeMux)) |
||||
light.SetProcessor(NewBlockProcessor(lightDb, FakePow{}, light, new(event.TypeMux))) |
||||
|
||||
if n, err := light.InsertHeaderChain(headers, 1); err != nil { |
||||
t.Fatalf("failed to insert header %d: %v", n, err) |
||||
} |
||||
assert(t, "light", light, height, 0, 0) |
||||
light.Rollback(remove) |
||||
assert(t, "light", light, height/2, 0, 0) |
||||
} |
||||
|
||||
// Tests that chain reorganizations handle transaction removals and reinsertions.
|
||||
func TestChainTxReorgs(t *testing.T) { |
||||
params.MinGasLimit = big.NewInt(125000) // Minimum the gas limit may ever be.
|
||||
params.GenesisGasLimit = big.NewInt(3141592) // Gas limit of the Genesis block.
|
||||
|
||||
var ( |
||||
key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") |
||||
key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") |
||||
key3, _ = crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee") |
||||
addr1 = crypto.PubkeyToAddress(key1.PublicKey) |
||||
addr2 = crypto.PubkeyToAddress(key2.PublicKey) |
||||
addr3 = crypto.PubkeyToAddress(key3.PublicKey) |
||||
db, _ = ethdb.NewMemDatabase() |
||||
) |
||||
genesis := WriteGenesisBlockForTesting(db, |
||||
GenesisAccount{addr1, big.NewInt(1000000)}, |
||||
GenesisAccount{addr2, big.NewInt(1000000)}, |
||||
GenesisAccount{addr3, big.NewInt(1000000)}, |
||||
) |
||||
// Create two transactions shared between the chains:
|
||||
// - postponed: transaction included at a later block in the forked chain
|
||||
// - swapped: transaction included at the same block number in the forked chain
|
||||
postponed, _ := types.NewTransaction(0, addr1, big.NewInt(1000), params.TxGas, nil, nil).SignECDSA(key1) |
||||
swapped, _ := types.NewTransaction(1, addr1, big.NewInt(1000), params.TxGas, nil, nil).SignECDSA(key1) |
||||
|
||||
// Create two transactions that will be dropped by the forked chain:
|
||||
// - pastDrop: transaction dropped retroactively from a past block
|
||||
// - freshDrop: transaction dropped exactly at the block where the reorg is detected
|
||||
var pastDrop, freshDrop *types.Transaction |
||||
|
||||
// Create three transactions that will be added in the forked chain:
|
||||
// - pastAdd: transaction added before the reorganiztion is detected
|
||||
// - freshAdd: transaction added at the exact block the reorg is detected
|
||||
// - futureAdd: transaction added after the reorg has already finished
|
||||
var pastAdd, freshAdd, futureAdd *types.Transaction |
||||
|
||||
chain, _ := GenerateChain(genesis, db, 3, func(i int, gen *BlockGen) { |
||||
switch i { |
||||
case 0: |
||||
pastDrop, _ = types.NewTransaction(gen.TxNonce(addr2), addr2, big.NewInt(1000), params.TxGas, nil, nil).SignECDSA(key2) |
||||
|
||||
gen.AddTx(pastDrop) // This transaction will be dropped in the fork from below the split point
|
||||
gen.AddTx(postponed) // This transaction will be postponed till block #3 in the fork
|
||||
|
||||
case 2: |
||||
freshDrop, _ = types.NewTransaction(gen.TxNonce(addr2), addr2, big.NewInt(1000), params.TxGas, nil, nil).SignECDSA(key2) |
||||
|
||||
gen.AddTx(freshDrop) // This transaction will be dropped in the fork from exactly at the split point
|
||||
gen.AddTx(swapped) // This transaction will be swapped out at the exact height
|
||||
|
||||
gen.OffsetTime(9) // Lower the block difficulty to simulate a weaker chain
|
||||
} |
||||
}) |
||||
// Import the chain. This runs all block validation rules.
|
||||
evmux := &event.TypeMux{} |
||||
chainman, _ := NewBlockChain(db, FakePow{}, evmux) |
||||
chainman.SetProcessor(NewBlockProcessor(db, FakePow{}, chainman, evmux)) |
||||
if i, err := chainman.InsertChain(chain); err != nil { |
||||
t.Fatalf("failed to insert original chain[%d]: %v", i, err) |
||||
} |
||||
|
||||
// overwrite the old chain
|
||||
chain, _ = GenerateChain(genesis, db, 5, func(i int, gen *BlockGen) { |
||||
switch i { |
||||
case 0: |
||||
pastAdd, _ = types.NewTransaction(gen.TxNonce(addr3), addr3, big.NewInt(1000), params.TxGas, nil, nil).SignECDSA(key3) |
||||
gen.AddTx(pastAdd) // This transaction needs to be injected during reorg
|
||||
|
||||
case 2: |
||||
gen.AddTx(postponed) // This transaction was postponed from block #1 in the original chain
|
||||
gen.AddTx(swapped) // This transaction was swapped from the exact current spot in the original chain
|
||||
|
||||
freshAdd, _ = types.NewTransaction(gen.TxNonce(addr3), addr3, big.NewInt(1000), params.TxGas, nil, nil).SignECDSA(key3) |
||||
gen.AddTx(freshAdd) // This transaction will be added exactly at reorg time
|
||||
|
||||
case 3: |
||||
futureAdd, _ = types.NewTransaction(gen.TxNonce(addr3), addr3, big.NewInt(1000), params.TxGas, nil, nil).SignECDSA(key3) |
||||
gen.AddTx(futureAdd) // This transaction will be added after a full reorg
|
||||
} |
||||
}) |
||||
if _, err := chainman.InsertChain(chain); err != nil { |
||||
t.Fatalf("failed to insert forked chain: %v", err) |
||||
} |
||||
|
||||
// removed tx
|
||||
for i, tx := range (types.Transactions{pastDrop, freshDrop}) { |
||||
if GetTransaction(db, tx.Hash()) != nil { |
||||
t.Errorf("drop %d: tx found while shouldn't have been", i) |
||||
} |
||||
if GetReceipt(db, tx.Hash()) != nil { |
||||
t.Errorf("drop %d: receipt found while shouldn't have been", i) |
||||
} |
||||
} |
||||
// added tx
|
||||
for i, tx := range (types.Transactions{pastAdd, freshAdd, futureAdd}) { |
||||
if GetTransaction(db, tx.Hash()) == nil { |
||||
t.Errorf("add %d: expected tx to be found", i) |
||||
} |
||||
if GetReceipt(db, tx.Hash()) == nil { |
||||
t.Errorf("add %d: expected receipt to be found", i) |
||||
} |
||||
} |
||||
// shared tx
|
||||
for i, tx := range (types.Transactions{postponed, swapped}) { |
||||
if GetTransaction(db, tx.Hash()) == nil { |
||||
t.Errorf("share %d: expected tx to be found", i) |
||||
} |
||||
if GetReceipt(db, tx.Hash()) == nil { |
||||
t.Errorf("share %d: expected receipt to be found", i) |
||||
} |
||||
} |
||||
} |
@ -1,847 +0,0 @@ |
||||
// Copyright 2014 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 core implements the Ethereum consensus protocol.
|
||||
package core |
||||
|
||||
import ( |
||||
"errors" |
||||
"fmt" |
||||
"io" |
||||
"math/big" |
||||
"sync" |
||||
"sync/atomic" |
||||
"time" |
||||
|
||||
"github.com/ethereum/go-ethereum/common" |
||||
"github.com/ethereum/go-ethereum/core/state" |
||||
"github.com/ethereum/go-ethereum/core/types" |
||||
"github.com/ethereum/go-ethereum/ethdb" |
||||
"github.com/ethereum/go-ethereum/event" |
||||
"github.com/ethereum/go-ethereum/logger" |
||||
"github.com/ethereum/go-ethereum/logger/glog" |
||||
"github.com/ethereum/go-ethereum/metrics" |
||||
"github.com/ethereum/go-ethereum/pow" |
||||
"github.com/ethereum/go-ethereum/rlp" |
||||
"github.com/hashicorp/golang-lru" |
||||
) |
||||
|
||||
var ( |
||||
chainlogger = logger.NewLogger("CHAIN") |
||||
jsonlogger = logger.NewJsonLogger() |
||||
|
||||
blockInsertTimer = metrics.NewTimer("chain/inserts") |
||||
|
||||
ErrNoGenesis = errors.New("Genesis not found in chain") |
||||
) |
||||
|
||||
const ( |
||||
headerCacheLimit = 512 |
||||
bodyCacheLimit = 256 |
||||
tdCacheLimit = 1024 |
||||
blockCacheLimit = 256 |
||||
maxFutureBlocks = 256 |
||||
maxTimeFutureBlocks = 30 |
||||
checkpointLimit = 200 |
||||
) |
||||
|
||||
type ChainManager struct { |
||||
//eth EthManager
|
||||
chainDb ethdb.Database |
||||
processor types.BlockProcessor |
||||
eventMux *event.TypeMux |
||||
genesisBlock *types.Block |
||||
// Last known total difficulty
|
||||
mu sync.RWMutex |
||||
chainmu sync.RWMutex |
||||
tsmu sync.RWMutex |
||||
|
||||
checkpoint int // checkpoint counts towards the new checkpoint
|
||||
td *big.Int |
||||
currentBlock *types.Block |
||||
currentGasLimit *big.Int |
||||
|
||||
headerCache *lru.Cache // Cache for the most recent block headers
|
||||
bodyCache *lru.Cache // Cache for the most recent block bodies
|
||||
bodyRLPCache *lru.Cache // Cache for the most recent block bodies in RLP encoded format
|
||||
tdCache *lru.Cache // Cache for the most recent block total difficulties
|
||||
blockCache *lru.Cache // Cache for the most recent entire blocks
|
||||
futureBlocks *lru.Cache // future blocks are blocks added for later processing
|
||||
|
||||
quit chan struct{} |
||||
running int32 // running must be called automically
|
||||
// procInterrupt must be atomically called
|
||||
procInterrupt int32 // interrupt signaler for block processing
|
||||
wg sync.WaitGroup |
||||
|
||||
pow pow.PoW |
||||
} |
||||
|
||||
func NewChainManager(chainDb ethdb.Database, pow pow.PoW, mux *event.TypeMux) (*ChainManager, error) { |
||||
headerCache, _ := lru.New(headerCacheLimit) |
||||
bodyCache, _ := lru.New(bodyCacheLimit) |
||||
bodyRLPCache, _ := lru.New(bodyCacheLimit) |
||||
tdCache, _ := lru.New(tdCacheLimit) |
||||
blockCache, _ := lru.New(blockCacheLimit) |
||||
futureBlocks, _ := lru.New(maxFutureBlocks) |
||||
|
||||
bc := &ChainManager{ |
||||
chainDb: chainDb, |
||||
eventMux: mux, |
||||
quit: make(chan struct{}), |
||||
headerCache: headerCache, |
||||
bodyCache: bodyCache, |
||||
bodyRLPCache: bodyRLPCache, |
||||
tdCache: tdCache, |
||||
blockCache: blockCache, |
||||
futureBlocks: futureBlocks, |
||||
pow: pow, |
||||
} |
||||
|
||||
bc.genesisBlock = bc.GetBlockByNumber(0) |
||||
if bc.genesisBlock == nil { |
||||
reader, err := NewDefaultGenesisReader() |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
bc.genesisBlock, err = WriteGenesisBlock(chainDb, reader) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
glog.V(logger.Info).Infoln("WARNING: Wrote default ethereum genesis block") |
||||
} |
||||
if err := bc.setLastState(); err != nil { |
||||
return nil, err |
||||
} |
||||
// Check the current state of the block hashes and make sure that we do not have any of the bad blocks in our chain
|
||||
for hash, _ := range BadHashes { |
||||
if block := bc.GetBlock(hash); block != nil { |
||||
glog.V(logger.Error).Infof("Found bad hash. Reorganising chain to state %x\n", block.ParentHash().Bytes()[:4]) |
||||
block = bc.GetBlock(block.ParentHash()) |
||||
if block == nil { |
||||
glog.Fatal("Unable to complete. Parent block not found. Corrupted DB?") |
||||
} |
||||
bc.SetHead(block) |
||||
|
||||
glog.V(logger.Error).Infoln("Chain reorg was successfull. Resuming normal operation") |
||||
} |
||||
} |
||||
// Take ownership of this particular state
|
||||
go bc.update() |
||||
return bc, nil |
||||
} |
||||
|
||||
func (bc *ChainManager) SetHead(head *types.Block) { |
||||
bc.mu.Lock() |
||||
defer bc.mu.Unlock() |
||||
|
||||
for block := bc.currentBlock; block != nil && block.Hash() != head.Hash(); block = bc.GetBlock(block.ParentHash()) { |
||||
DeleteBlock(bc.chainDb, block.Hash()) |
||||
} |
||||
bc.headerCache.Purge() |
||||
bc.bodyCache.Purge() |
||||
bc.bodyRLPCache.Purge() |
||||
bc.blockCache.Purge() |
||||
bc.futureBlocks.Purge() |
||||
|
||||
bc.currentBlock = head |
||||
bc.setTotalDifficulty(bc.GetTd(head.Hash())) |
||||
bc.insert(head) |
||||
bc.setLastState() |
||||
} |
||||
|
||||
func (self *ChainManager) Td() *big.Int { |
||||
self.mu.RLock() |
||||
defer self.mu.RUnlock() |
||||
|
||||
return new(big.Int).Set(self.td) |
||||
} |
||||
|
||||
func (self *ChainManager) GasLimit() *big.Int { |
||||
self.mu.RLock() |
||||
defer self.mu.RUnlock() |
||||
|
||||
return self.currentBlock.GasLimit() |
||||
} |
||||
|
||||
func (self *ChainManager) LastBlockHash() common.Hash { |
||||
self.mu.RLock() |
||||
defer self.mu.RUnlock() |
||||
|
||||
return self.currentBlock.Hash() |
||||
} |
||||
|
||||
func (self *ChainManager) CurrentBlock() *types.Block { |
||||
self.mu.RLock() |
||||
defer self.mu.RUnlock() |
||||
|
||||
return self.currentBlock |
||||
} |
||||
|
||||
func (self *ChainManager) Status() (td *big.Int, currentBlock common.Hash, genesisBlock common.Hash) { |
||||
self.mu.RLock() |
||||
defer self.mu.RUnlock() |
||||
|
||||
return new(big.Int).Set(self.td), self.currentBlock.Hash(), self.genesisBlock.Hash() |
||||
} |
||||
|
||||
func (self *ChainManager) SetProcessor(proc types.BlockProcessor) { |
||||
self.processor = proc |
||||
} |
||||
|
||||
func (self *ChainManager) State() *state.StateDB { |
||||
return state.New(self.CurrentBlock().Root(), self.chainDb) |
||||
} |
||||
|
||||
func (bc *ChainManager) recover() bool { |
||||
data, _ := bc.chainDb.Get([]byte("checkpoint")) |
||||
if len(data) != 0 { |
||||
block := bc.GetBlock(common.BytesToHash(data)) |
||||
if block != nil { |
||||
if err := WriteCanonicalHash(bc.chainDb, block.Hash(), block.NumberU64()); err != nil { |
||||
glog.Fatalf("failed to write database head number: %v", err) |
||||
} |
||||
if err := WriteHeadBlockHash(bc.chainDb, block.Hash()); err != nil { |
||||
glog.Fatalf("failed to write database head hash: %v", err) |
||||
} |
||||
bc.currentBlock = block |
||||
return true |
||||
} |
||||
} |
||||
return false |
||||
} |
||||
|
||||
func (bc *ChainManager) setLastState() error { |
||||
head := GetHeadBlockHash(bc.chainDb) |
||||
if head != (common.Hash{}) { |
||||
block := bc.GetBlock(head) |
||||
if block != nil { |
||||
bc.currentBlock = block |
||||
} else { |
||||
glog.Infof("LastBlock (%x) not found. Recovering...\n", head) |
||||
if bc.recover() { |
||||
glog.Infof("Recover successful") |
||||
} else { |
||||
glog.Fatalf("Recover failed. Please report") |
||||
} |
||||
} |
||||
} else { |
||||
bc.Reset() |
||||
} |
||||
bc.td = bc.GetTd(bc.currentBlock.Hash()) |
||||
bc.currentGasLimit = CalcGasLimit(bc.currentBlock) |
||||
|
||||
if glog.V(logger.Info) { |
||||
glog.Infof("Last block (#%v) %x TD=%v\n", bc.currentBlock.Number(), bc.currentBlock.Hash(), bc.td) |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
// Reset purges the entire blockchain, restoring it to its genesis state.
|
||||
func (bc *ChainManager) Reset() { |
||||
bc.ResetWithGenesisBlock(bc.genesisBlock) |
||||
} |
||||
|
||||
// ResetWithGenesisBlock purges the entire blockchain, restoring it to the
|
||||
// specified genesis state.
|
||||
func (bc *ChainManager) ResetWithGenesisBlock(genesis *types.Block) { |
||||
bc.mu.Lock() |
||||
defer bc.mu.Unlock() |
||||
|
||||
// Dump the entire block chain and purge the caches
|
||||
for block := bc.currentBlock; block != nil; block = bc.GetBlock(block.ParentHash()) { |
||||
DeleteBlock(bc.chainDb, block.Hash()) |
||||
} |
||||
bc.headerCache.Purge() |
||||
bc.bodyCache.Purge() |
||||
bc.bodyRLPCache.Purge() |
||||
bc.blockCache.Purge() |
||||
bc.futureBlocks.Purge() |
||||
|
||||
// Prepare the genesis block and reinitialize the chain
|
||||
if err := WriteTd(bc.chainDb, genesis.Hash(), genesis.Difficulty()); err != nil { |
||||
glog.Fatalf("failed to write genesis block TD: %v", err) |
||||
} |
||||
if err := WriteBlock(bc.chainDb, genesis); err != nil { |
||||
glog.Fatalf("failed to write genesis block: %v", err) |
||||
} |
||||
bc.genesisBlock = genesis |
||||
bc.insert(bc.genesisBlock) |
||||
bc.currentBlock = bc.genesisBlock |
||||
bc.setTotalDifficulty(genesis.Difficulty()) |
||||
} |
||||
|
||||
// Export writes the active chain to the given writer.
|
||||
func (self *ChainManager) Export(w io.Writer) error { |
||||
if err := self.ExportN(w, uint64(0), self.currentBlock.NumberU64()); err != nil { |
||||
return err |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// ExportN writes a subset of the active chain to the given writer.
|
||||
func (self *ChainManager) ExportN(w io.Writer, first uint64, last uint64) error { |
||||
self.mu.RLock() |
||||
defer self.mu.RUnlock() |
||||
|
||||
if first > last { |
||||
return fmt.Errorf("export failed: first (%d) is greater than last (%d)", first, last) |
||||
} |
||||
|
||||
glog.V(logger.Info).Infof("exporting %d blocks...\n", last-first+1) |
||||
|
||||
for nr := first; nr <= last; nr++ { |
||||
block := self.GetBlockByNumber(nr) |
||||
if block == nil { |
||||
return fmt.Errorf("export failed on #%d: not found", nr) |
||||
} |
||||
|
||||
if err := block.EncodeRLP(w); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
// insert injects a block into the current chain block chain. Note, this function
|
||||
// assumes that the `mu` mutex is held!
|
||||
func (bc *ChainManager) insert(block *types.Block) { |
||||
// Add the block to the canonical chain number scheme and mark as the head
|
||||
if err := WriteCanonicalHash(bc.chainDb, block.Hash(), block.NumberU64()); err != nil { |
||||
glog.Fatalf("failed to insert block number: %v", err) |
||||
} |
||||
if err := WriteHeadBlockHash(bc.chainDb, block.Hash()); err != nil { |
||||
glog.Fatalf("failed to insert block number: %v", err) |
||||
} |
||||
// Add a new restore point if we reached some limit
|
||||
bc.checkpoint++ |
||||
if bc.checkpoint > checkpointLimit { |
||||
if err := bc.chainDb.Put([]byte("checkpoint"), block.Hash().Bytes()); err != nil { |
||||
glog.Fatalf("failed to create checkpoint: %v", err) |
||||
} |
||||
bc.checkpoint = 0 |
||||
} |
||||
// Update the internal internal state with the head block
|
||||
bc.currentBlock = block |
||||
} |
||||
|
||||
// Accessors
|
||||
func (bc *ChainManager) Genesis() *types.Block { |
||||
return bc.genesisBlock |
||||
} |
||||
|
||||
// HasHeader checks if a block header is present in the database or not, caching
|
||||
// it if present.
|
||||
func (bc *ChainManager) HasHeader(hash common.Hash) bool { |
||||
return bc.GetHeader(hash) != nil |
||||
} |
||||
|
||||
// GetHeader retrieves a block header from the database by hash, caching it if
|
||||
// found.
|
||||
func (self *ChainManager) GetHeader(hash common.Hash) *types.Header { |
||||
// Short circuit if the header's already in the cache, retrieve otherwise
|
||||
if header, ok := self.headerCache.Get(hash); ok { |
||||
return header.(*types.Header) |
||||
} |
||||
header := GetHeader(self.chainDb, hash) |
||||
if header == nil { |
||||
return nil |
||||
} |
||||
// Cache the found header for next time and return
|
||||
self.headerCache.Add(header.Hash(), header) |
||||
return header |
||||
} |
||||
|
||||
// GetHeaderByNumber retrieves a block header from the database by number,
|
||||
// caching it (associated with its hash) if found.
|
||||
func (self *ChainManager) GetHeaderByNumber(number uint64) *types.Header { |
||||
hash := GetCanonicalHash(self.chainDb, number) |
||||
if hash == (common.Hash{}) { |
||||
return nil |
||||
} |
||||
return self.GetHeader(hash) |
||||
} |
||||
|
||||
// GetBody retrieves a block body (transactions and uncles) from the database by
|
||||
// hash, caching it if found.
|
||||
func (self *ChainManager) GetBody(hash common.Hash) *types.Body { |
||||
// Short circuit if the body's already in the cache, retrieve otherwise
|
||||
if cached, ok := self.bodyCache.Get(hash); ok { |
||||
body := cached.(*types.Body) |
||||
return body |
||||
} |
||||
body := GetBody(self.chainDb, hash) |
||||
if body == nil { |
||||
return nil |
||||
} |
||||
// Cache the found body for next time and return
|
||||
self.bodyCache.Add(hash, body) |
||||
return body |
||||
} |
||||
|
||||
// GetBodyRLP retrieves a block body in RLP encoding from the database by hash,
|
||||
// caching it if found.
|
||||
func (self *ChainManager) GetBodyRLP(hash common.Hash) rlp.RawValue { |
||||
// Short circuit if the body's already in the cache, retrieve otherwise
|
||||
if cached, ok := self.bodyRLPCache.Get(hash); ok { |
||||
return cached.(rlp.RawValue) |
||||
} |
||||
body := GetBodyRLP(self.chainDb, hash) |
||||
if len(body) == 0 { |
||||
return nil |
||||
} |
||||
// Cache the found body for next time and return
|
||||
self.bodyRLPCache.Add(hash, body) |
||||
return body |
||||
} |
||||
|
||||
// GetTd retrieves a block's total difficulty in the canonical chain from the
|
||||
// database by hash, caching it if found.
|
||||
func (self *ChainManager) GetTd(hash common.Hash) *big.Int { |
||||
// Short circuit if the td's already in the cache, retrieve otherwise
|
||||
if cached, ok := self.tdCache.Get(hash); ok { |
||||
return cached.(*big.Int) |
||||
} |
||||
td := GetTd(self.chainDb, hash) |
||||
if td == nil { |
||||
return nil |
||||
} |
||||
// Cache the found body for next time and return
|
||||
self.tdCache.Add(hash, td) |
||||
return td |
||||
} |
||||
|
||||
// HasBlock checks if a block is fully present in the database or not, caching
|
||||
// it if present.
|
||||
func (bc *ChainManager) HasBlock(hash common.Hash) bool { |
||||
return bc.GetBlock(hash) != nil |
||||
} |
||||
|
||||
// GetBlock retrieves a block from the database by hash, caching it if found.
|
||||
func (self *ChainManager) GetBlock(hash common.Hash) *types.Block { |
||||
// Short circuit if the block's already in the cache, retrieve otherwise
|
||||
if block, ok := self.blockCache.Get(hash); ok { |
||||
return block.(*types.Block) |
||||
} |
||||
block := GetBlock(self.chainDb, hash) |
||||
if block == nil { |
||||
return nil |
||||
} |
||||
// Cache the found block for next time and return
|
||||
self.blockCache.Add(block.Hash(), block) |
||||
return block |
||||
} |
||||
|
||||
// GetBlockByNumber retrieves a block from the database by number, caching it
|
||||
// (associated with its hash) if found.
|
||||
func (self *ChainManager) GetBlockByNumber(number uint64) *types.Block { |
||||
hash := GetCanonicalHash(self.chainDb, number) |
||||
if hash == (common.Hash{}) { |
||||
return nil |
||||
} |
||||
return self.GetBlock(hash) |
||||
} |
||||
|
||||
// GetBlockHashesFromHash retrieves a number of block hashes starting at a given
|
||||
// hash, fetching towards the genesis block.
|
||||
func (self *ChainManager) GetBlockHashesFromHash(hash common.Hash, max uint64) []common.Hash { |
||||
// Get the origin header from which to fetch
|
||||
header := self.GetHeader(hash) |
||||
if header == nil { |
||||
return nil |
||||
} |
||||
// Iterate the headers until enough is collected or the genesis reached
|
||||
chain := make([]common.Hash, 0, max) |
||||
for i := uint64(0); i < max; i++ { |
||||
if header = self.GetHeader(header.ParentHash); header == nil { |
||||
break |
||||
} |
||||
chain = append(chain, header.Hash()) |
||||
if header.Number.Cmp(common.Big0) == 0 { |
||||
break |
||||
} |
||||
} |
||||
return chain |
||||
} |
||||
|
||||
// [deprecated by eth/62]
|
||||
// GetBlocksFromHash returns the block corresponding to hash and up to n-1 ancestors.
|
||||
func (self *ChainManager) GetBlocksFromHash(hash common.Hash, n int) (blocks []*types.Block) { |
||||
for i := 0; i < n; i++ { |
||||
block := self.GetBlock(hash) |
||||
if block == nil { |
||||
break |
||||
} |
||||
blocks = append(blocks, block) |
||||
hash = block.ParentHash() |
||||
} |
||||
return |
||||
} |
||||
|
||||
func (self *ChainManager) GetUnclesInChain(block *types.Block, length int) (uncles []*types.Header) { |
||||
for i := 0; block != nil && i < length; i++ { |
||||
uncles = append(uncles, block.Uncles()...) |
||||
block = self.GetBlock(block.ParentHash()) |
||||
} |
||||
|
||||
return |
||||
} |
||||
|
||||
// setTotalDifficulty updates the TD of the chain manager. Note, this function
|
||||
// assumes that the `mu` mutex is held!
|
||||
func (bc *ChainManager) setTotalDifficulty(td *big.Int) { |
||||
bc.td = new(big.Int).Set(td) |
||||
} |
||||
|
||||
func (bc *ChainManager) Stop() { |
||||
if !atomic.CompareAndSwapInt32(&bc.running, 0, 1) { |
||||
return |
||||
} |
||||
close(bc.quit) |
||||
atomic.StoreInt32(&bc.procInterrupt, 1) |
||||
|
||||
bc.wg.Wait() |
||||
|
||||
glog.V(logger.Info).Infoln("Chain manager stopped") |
||||
} |
||||
|
||||
type queueEvent struct { |
||||
queue []interface{} |
||||
canonicalCount int |
||||
sideCount int |
||||
splitCount int |
||||
} |
||||
|
||||
func (self *ChainManager) procFutureBlocks() { |
||||
blocks := make([]*types.Block, self.futureBlocks.Len()) |
||||
for i, hash := range self.futureBlocks.Keys() { |
||||
block, _ := self.futureBlocks.Get(hash) |
||||
blocks[i] = block.(*types.Block) |
||||
} |
||||
if len(blocks) > 0 { |
||||
types.BlockBy(types.Number).Sort(blocks) |
||||
self.InsertChain(blocks) |
||||
} |
||||
} |
||||
|
||||
type writeStatus byte |
||||
|
||||
const ( |
||||
NonStatTy writeStatus = iota |
||||
CanonStatTy |
||||
SplitStatTy |
||||
SideStatTy |
||||
) |
||||
|
||||
// WriteBlock writes the block to the chain.
|
||||
func (self *ChainManager) WriteBlock(block *types.Block) (status writeStatus, err error) { |
||||
self.wg.Add(1) |
||||
defer self.wg.Done() |
||||
|
||||
// Calculate the total difficulty of the block
|
||||
ptd := self.GetTd(block.ParentHash()) |
||||
if ptd == nil { |
||||
return NonStatTy, ParentError(block.ParentHash()) |
||||
} |
||||
td := new(big.Int).Add(block.Difficulty(), ptd) |
||||
|
||||
self.mu.RLock() |
||||
cblock := self.currentBlock |
||||
self.mu.RUnlock() |
||||
|
||||
// Compare the TD of the last known block in the canonical chain to make sure it's greater.
|
||||
// At this point it's possible that a different chain (fork) becomes the new canonical chain.
|
||||
if td.Cmp(self.Td()) > 0 { |
||||
// chain fork
|
||||
if block.ParentHash() != cblock.Hash() { |
||||
// during split we merge two different chains and create the new canonical chain
|
||||
err := self.reorg(cblock, block) |
||||
if err != nil { |
||||
return NonStatTy, err |
||||
} |
||||
} |
||||
status = CanonStatTy |
||||
|
||||
self.mu.Lock() |
||||
self.setTotalDifficulty(td) |
||||
self.insert(block) |
||||
self.mu.Unlock() |
||||
} else { |
||||
status = SideStatTy |
||||
} |
||||
|
||||
if err := WriteTd(self.chainDb, block.Hash(), td); err != nil { |
||||
glog.Fatalf("failed to write block total difficulty: %v", err) |
||||
} |
||||
if err := WriteBlock(self.chainDb, block); err != nil { |
||||
glog.Fatalf("filed to write block contents: %v", err) |
||||
} |
||||
// Delete from future blocks
|
||||
self.futureBlocks.Remove(block.Hash()) |
||||
|
||||
return |
||||
} |
||||
|
||||
// InsertChain will attempt to insert the given chain in to the canonical chain or, otherwise, create a fork. It an error is returned
|
||||
// it will return the index number of the failing block as well an error describing what went wrong (for possible errors see core/errors.go).
|
||||
func (self *ChainManager) InsertChain(chain types.Blocks) (int, error) { |
||||
self.wg.Add(1) |
||||
defer self.wg.Done() |
||||
|
||||
self.chainmu.Lock() |
||||
defer self.chainmu.Unlock() |
||||
|
||||
// A queued approach to delivering events. This is generally
|
||||
// faster than direct delivery and requires much less mutex
|
||||
// acquiring.
|
||||
var ( |
||||
queue = make([]interface{}, len(chain)) |
||||
queueEvent = queueEvent{queue: queue} |
||||
stats struct{ queued, processed, ignored int } |
||||
tstart = time.Now() |
||||
|
||||
nonceChecked = make([]bool, len(chain)) |
||||
) |
||||
|
||||
// Start the parallel nonce verifier.
|
||||
nonceAbort, nonceResults := verifyNoncesFromBlocks(self.pow, chain) |
||||
defer close(nonceAbort) |
||||
|
||||
txcount := 0 |
||||
for i, block := range chain { |
||||
if atomic.LoadInt32(&self.procInterrupt) == 1 { |
||||
glog.V(logger.Debug).Infoln("Premature abort during chain processing") |
||||
break |
||||
} |
||||
|
||||
bstart := time.Now() |
||||
// Wait for block i's nonce to be verified before processing
|
||||
// its state transition.
|
||||
for !nonceChecked[i] { |
||||
r := <-nonceResults |
||||
nonceChecked[r.index] = true |
||||
if !r.valid { |
||||
block := chain[r.index] |
||||
return r.index, &BlockNonceErr{Hash: block.Hash(), Number: block.Number(), Nonce: block.Nonce()} |
||||
} |
||||
} |
||||
|
||||
if BadHashes[block.Hash()] { |
||||
err := BadHashError(block.Hash()) |
||||
blockErr(block, err) |
||||
return i, err |
||||
} |
||||
// Call in to the block processor and check for errors. It's likely that if one block fails
|
||||
// all others will fail too (unless a known block is returned).
|
||||
logs, receipts, err := self.processor.Process(block) |
||||
if err != nil { |
||||
if IsKnownBlockErr(err) { |
||||
stats.ignored++ |
||||
continue |
||||
} |
||||
|
||||
if err == BlockFutureErr { |
||||
// Allow up to MaxFuture second in the future blocks. If this limit
|
||||
// is exceeded the chain is discarded and processed at a later time
|
||||
// if given.
|
||||
max := big.NewInt(time.Now().Unix() + maxTimeFutureBlocks) |
||||
if block.Time().Cmp(max) == 1 { |
||||
return i, fmt.Errorf("%v: BlockFutureErr, %v > %v", BlockFutureErr, block.Time(), max) |
||||
} |
||||
|
||||
self.futureBlocks.Add(block.Hash(), block) |
||||
stats.queued++ |
||||
continue |
||||
} |
||||
|
||||
if IsParentErr(err) && self.futureBlocks.Contains(block.ParentHash()) { |
||||
self.futureBlocks.Add(block.Hash(), block) |
||||
stats.queued++ |
||||
continue |
||||
} |
||||
|
||||
blockErr(block, err) |
||||
|
||||
go ReportBlock(block, err) |
||||
|
||||
return i, err |
||||
} |
||||
if err := PutBlockReceipts(self.chainDb, block, receipts); err != nil { |
||||
glog.V(logger.Warn).Infoln("error writing block receipts:", err) |
||||
} |
||||
|
||||
txcount += len(block.Transactions()) |
||||
// write the block to the chain and get the status
|
||||
status, err := self.WriteBlock(block) |
||||
if err != nil { |
||||
return i, err |
||||
} |
||||
switch status { |
||||
case CanonStatTy: |
||||
if glog.V(logger.Debug) { |
||||
glog.Infof("[%v] inserted block #%d (%d TXs %v G %d UNCs) (%x...). Took %v\n", time.Now().UnixNano(), block.Number(), len(block.Transactions()), block.GasUsed(), len(block.Uncles()), block.Hash().Bytes()[0:4], time.Since(bstart)) |
||||
} |
||||
queue[i] = ChainEvent{block, block.Hash(), logs} |
||||
queueEvent.canonicalCount++ |
||||
|
||||
// This puts transactions in a extra db for rpc
|
||||
PutTransactions(self.chainDb, block, block.Transactions()) |
||||
// store the receipts
|
||||
PutReceipts(self.chainDb, receipts) |
||||
case SideStatTy: |
||||
if glog.V(logger.Detail) { |
||||
glog.Infof("inserted forked block #%d (TD=%v) (%d TXs %d UNCs) (%x...). Took %v\n", block.Number(), block.Difficulty(), len(block.Transactions()), len(block.Uncles()), block.Hash().Bytes()[0:4], time.Since(bstart)) |
||||
} |
||||
queue[i] = ChainSideEvent{block, logs} |
||||
queueEvent.sideCount++ |
||||
case SplitStatTy: |
||||
queue[i] = ChainSplitEvent{block, logs} |
||||
queueEvent.splitCount++ |
||||
} |
||||
stats.processed++ |
||||
} |
||||
|
||||
if (stats.queued > 0 || stats.processed > 0 || stats.ignored > 0) && bool(glog.V(logger.Info)) { |
||||
tend := time.Since(tstart) |
||||
start, end := chain[0], chain[len(chain)-1] |
||||
glog.Infof("imported %d block(s) (%d queued %d ignored) including %d txs in %v. #%v [%x / %x]\n", stats.processed, stats.queued, stats.ignored, txcount, tend, end.Number(), start.Hash().Bytes()[:4], end.Hash().Bytes()[:4]) |
||||
} |
||||
|
||||
go self.eventMux.Post(queueEvent) |
||||
|
||||
return 0, nil |
||||
} |
||||
|
||||
// reorgs takes two blocks, an old chain and a new chain and will reconstruct the blocks and inserts them
|
||||
// to be part of the new canonical chain and accumulates potential missing transactions and post an
|
||||
// event about them
|
||||
func (self *ChainManager) reorg(oldBlock, newBlock *types.Block) error { |
||||
self.mu.Lock() |
||||
defer self.mu.Unlock() |
||||
|
||||
var ( |
||||
newChain types.Blocks |
||||
commonBlock *types.Block |
||||
oldStart = oldBlock |
||||
newStart = newBlock |
||||
deletedTxs types.Transactions |
||||
) |
||||
|
||||
// first reduce whoever is higher bound
|
||||
if oldBlock.NumberU64() > newBlock.NumberU64() { |
||||
// reduce old chain
|
||||
for oldBlock = oldBlock; oldBlock != nil && oldBlock.NumberU64() != newBlock.NumberU64(); oldBlock = self.GetBlock(oldBlock.ParentHash()) { |
||||
deletedTxs = append(deletedTxs, oldBlock.Transactions()...) |
||||
} |
||||
} else { |
||||
// reduce new chain and append new chain blocks for inserting later on
|
||||
for newBlock = newBlock; newBlock != nil && newBlock.NumberU64() != oldBlock.NumberU64(); newBlock = self.GetBlock(newBlock.ParentHash()) { |
||||
newChain = append(newChain, newBlock) |
||||
} |
||||
} |
||||
if oldBlock == nil { |
||||
return fmt.Errorf("Invalid old chain") |
||||
} |
||||
if newBlock == nil { |
||||
return fmt.Errorf("Invalid new chain") |
||||
} |
||||
|
||||
numSplit := newBlock.Number() |
||||
for { |
||||
if oldBlock.Hash() == newBlock.Hash() { |
||||
commonBlock = oldBlock |
||||
break |
||||
} |
||||
newChain = append(newChain, newBlock) |
||||
deletedTxs = append(deletedTxs, oldBlock.Transactions()...) |
||||
|
||||
oldBlock, newBlock = self.GetBlock(oldBlock.ParentHash()), self.GetBlock(newBlock.ParentHash()) |
||||
if oldBlock == nil { |
||||
return fmt.Errorf("Invalid old chain") |
||||
} |
||||
if newBlock == nil { |
||||
return fmt.Errorf("Invalid new chain") |
||||
} |
||||
} |
||||
|
||||
if glog.V(logger.Debug) { |
||||
commonHash := commonBlock.Hash() |
||||
glog.Infof("Chain split detected @ %x. Reorganising chain from #%v %x to %x", commonHash[:4], numSplit, oldStart.Hash().Bytes()[:4], newStart.Hash().Bytes()[:4]) |
||||
} |
||||
|
||||
var addedTxs types.Transactions |
||||
// insert blocks. Order does not matter. Last block will be written in ImportChain itself which creates the new head properly
|
||||
for _, block := range newChain { |
||||
// insert the block in the canonical way, re-writing history
|
||||
self.insert(block) |
||||
// write canonical receipts and transactions
|
||||
PutTransactions(self.chainDb, block, block.Transactions()) |
||||
PutReceipts(self.chainDb, GetBlockReceipts(self.chainDb, block.Hash())) |
||||
|
||||
addedTxs = append(addedTxs, block.Transactions()...) |
||||
} |
||||
|
||||
// calculate the difference between deleted and added transactions
|
||||
diff := types.TxDifference(deletedTxs, addedTxs) |
||||
// When transactions get deleted from the database that means the
|
||||
// receipts that were created in the fork must also be deleted
|
||||
for _, tx := range diff { |
||||
DeleteReceipt(self.chainDb, tx.Hash()) |
||||
DeleteTransaction(self.chainDb, tx.Hash()) |
||||
} |
||||
self.eventMux.Post(RemovedTransactionEvent{diff}) |
||||
|
||||
return nil |
||||
} |
||||
|
||||
func (self *ChainManager) update() { |
||||
events := self.eventMux.Subscribe(queueEvent{}) |
||||
futureTimer := time.Tick(5 * time.Second) |
||||
out: |
||||
for { |
||||
select { |
||||
case ev := <-events.Chan(): |
||||
switch ev := ev.(type) { |
||||
case queueEvent: |
||||
for _, event := range ev.queue { |
||||
switch event := event.(type) { |
||||
case ChainEvent: |
||||
// We need some control over the mining operation. Acquiring locks and waiting for the miner to create new block takes too long
|
||||
// and in most cases isn't even necessary.
|
||||
if self.currentBlock.Hash() == event.Hash { |
||||
self.currentGasLimit = CalcGasLimit(event.Block) |
||||
self.eventMux.Post(ChainHeadEvent{event.Block}) |
||||
} |
||||
} |
||||
self.eventMux.Post(event) |
||||
} |
||||
} |
||||
case <-futureTimer: |
||||
self.procFutureBlocks() |
||||
case <-self.quit: |
||||
break out |
||||
} |
||||
} |
||||
} |
||||
|
||||
func blockErr(block *types.Block, err error) { |
||||
h := block.Header() |
||||
glog.V(logger.Error).Infof("Bad block #%v (%x)\n", h.Number, h.Hash().Bytes()) |
||||
glog.V(logger.Error).Infoln(err) |
||||
glog.V(logger.Debug).Infoln(verifyNonces) |
||||
} |
@ -1,652 +0,0 @@ |
||||
// Copyright 2014 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 core |
||||
|
||||
import ( |
||||
"fmt" |
||||
"math/big" |
||||
"math/rand" |
||||
"os" |
||||
"path/filepath" |
||||
"runtime" |
||||
"strconv" |
||||
"testing" |
||||
|
||||
"github.com/ethereum/ethash" |
||||
"github.com/ethereum/go-ethereum/common" |
||||
"github.com/ethereum/go-ethereum/core/state" |
||||
"github.com/ethereum/go-ethereum/core/types" |
||||
"github.com/ethereum/go-ethereum/crypto" |
||||
"github.com/ethereum/go-ethereum/ethdb" |
||||
"github.com/ethereum/go-ethereum/event" |
||||
"github.com/ethereum/go-ethereum/params" |
||||
"github.com/ethereum/go-ethereum/pow" |
||||
"github.com/ethereum/go-ethereum/rlp" |
||||
"github.com/hashicorp/golang-lru" |
||||
) |
||||
|
||||
func init() { |
||||
runtime.GOMAXPROCS(runtime.NumCPU()) |
||||
} |
||||
|
||||
func thePow() pow.PoW { |
||||
pow, _ := ethash.NewForTesting() |
||||
return pow |
||||
} |
||||
|
||||
func theChainManager(db ethdb.Database, t *testing.T) *ChainManager { |
||||
var eventMux event.TypeMux |
||||
WriteTestNetGenesisBlock(db, 0) |
||||
chainMan, err := NewChainManager(db, thePow(), &eventMux) |
||||
if err != nil { |
||||
t.Error("failed creating chainmanager:", err) |
||||
t.FailNow() |
||||
return nil |
||||
} |
||||
blockMan := NewBlockProcessor(db, nil, chainMan, &eventMux) |
||||
chainMan.SetProcessor(blockMan) |
||||
|
||||
return chainMan |
||||
} |
||||
|
||||
// Test fork of length N starting from block i
|
||||
func testFork(t *testing.T, bman *BlockProcessor, i, N int, f func(td1, td2 *big.Int)) { |
||||
// switch databases to process the new chain
|
||||
db, err := ethdb.NewMemDatabase() |
||||
if err != nil { |
||||
t.Fatal("Failed to create db:", err) |
||||
} |
||||
// copy old chain up to i into new db with deterministic canonical
|
||||
bman2, err := newCanonical(i, db) |
||||
if err != nil { |
||||
t.Fatal("could not make new canonical in testFork", err) |
||||
} |
||||
// assert the bmans have the same block at i
|
||||
bi1 := bman.bc.GetBlockByNumber(uint64(i)).Hash() |
||||
bi2 := bman2.bc.GetBlockByNumber(uint64(i)).Hash() |
||||
if bi1 != bi2 { |
||||
fmt.Printf("%+v\n%+v\n\n", bi1, bi2) |
||||
t.Fatal("chains do not have the same hash at height", i) |
||||
} |
||||
bman2.bc.SetProcessor(bman2) |
||||
|
||||
// extend the fork
|
||||
parent := bman2.bc.CurrentBlock() |
||||
chainB := makeChain(parent, N, db, forkSeed) |
||||
_, err = bman2.bc.InsertChain(chainB) |
||||
if err != nil { |
||||
t.Fatal("Insert chain error for fork:", err) |
||||
} |
||||
|
||||
tdpre := bman.bc.Td() |
||||
// Test the fork's blocks on the original chain
|
||||
td, err := testChain(chainB, bman) |
||||
if err != nil { |
||||
t.Fatal("expected chainB not to give errors:", err) |
||||
} |
||||
// Compare difficulties
|
||||
f(tdpre, td) |
||||
|
||||
// Loop over parents making sure reconstruction is done properly
|
||||
} |
||||
|
||||
func printChain(bc *ChainManager) { |
||||
for i := bc.CurrentBlock().Number().Uint64(); i > 0; i-- { |
||||
b := bc.GetBlockByNumber(uint64(i)) |
||||
fmt.Printf("\t%x %v\n", b.Hash(), b.Difficulty()) |
||||
} |
||||
} |
||||
|
||||
// process blocks against a chain
|
||||
func testChain(chainB types.Blocks, bman *BlockProcessor) (*big.Int, error) { |
||||
for _, block := range chainB { |
||||
_, _, err := bman.bc.processor.Process(block) |
||||
if err != nil { |
||||
if IsKnownBlockErr(err) { |
||||
continue |
||||
} |
||||
return nil, err |
||||
} |
||||
bman.bc.mu.Lock() |
||||
WriteTd(bman.bc.chainDb, block.Hash(), new(big.Int).Add(block.Difficulty(), bman.bc.GetTd(block.ParentHash()))) |
||||
WriteBlock(bman.bc.chainDb, block) |
||||
bman.bc.mu.Unlock() |
||||
} |
||||
return bman.bc.GetTd(chainB[len(chainB)-1].Hash()), nil |
||||
} |
||||
|
||||
func loadChain(fn string, t *testing.T) (types.Blocks, error) { |
||||
fh, err := os.OpenFile(filepath.Join("..", "_data", fn), os.O_RDONLY, os.ModePerm) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
defer fh.Close() |
||||
|
||||
var chain types.Blocks |
||||
if err := rlp.Decode(fh, &chain); err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
return chain, nil |
||||
} |
||||
|
||||
func insertChain(done chan bool, chainMan *ChainManager, chain types.Blocks, t *testing.T) { |
||||
_, err := chainMan.InsertChain(chain) |
||||
if err != nil { |
||||
fmt.Println(err) |
||||
t.FailNow() |
||||
} |
||||
done <- true |
||||
} |
||||
|
||||
func TestExtendCanonical(t *testing.T) { |
||||
CanonicalLength := 5 |
||||
db, err := ethdb.NewMemDatabase() |
||||
if err != nil { |
||||
t.Fatal("Failed to create db:", err) |
||||
} |
||||
// make first chain starting from genesis
|
||||
bman, err := newCanonical(CanonicalLength, db) |
||||
if err != nil { |
||||
t.Fatal("Could not make new canonical chain:", err) |
||||
} |
||||
f := func(td1, td2 *big.Int) { |
||||
if td2.Cmp(td1) <= 0 { |
||||
t.Error("expected chainB to have higher difficulty. Got", td2, "expected more than", td1) |
||||
} |
||||
} |
||||
// Start fork from current height (CanonicalLength)
|
||||
testFork(t, bman, CanonicalLength, 1, f) |
||||
testFork(t, bman, CanonicalLength, 2, f) |
||||
testFork(t, bman, CanonicalLength, 5, f) |
||||
testFork(t, bman, CanonicalLength, 10, f) |
||||
} |
||||
|
||||
func TestShorterFork(t *testing.T) { |
||||
db, err := ethdb.NewMemDatabase() |
||||
if err != nil { |
||||
t.Fatal("Failed to create db:", err) |
||||
} |
||||
// make first chain starting from genesis
|
||||
bman, err := newCanonical(10, db) |
||||
if err != nil { |
||||
t.Fatal("Could not make new canonical chain:", err) |
||||
} |
||||
f := func(td1, td2 *big.Int) { |
||||
if td2.Cmp(td1) >= 0 { |
||||
t.Error("expected chainB to have lower difficulty. Got", td2, "expected less than", td1) |
||||
} |
||||
} |
||||
// Sum of numbers must be less than 10
|
||||
// for this to be a shorter fork
|
||||
testFork(t, bman, 0, 3, f) |
||||
testFork(t, bman, 0, 7, f) |
||||
testFork(t, bman, 1, 1, f) |
||||
testFork(t, bman, 1, 7, f) |
||||
testFork(t, bman, 5, 3, f) |
||||
testFork(t, bman, 5, 4, f) |
||||
} |
||||
|
||||
func TestLongerFork(t *testing.T) { |
||||
db, err := ethdb.NewMemDatabase() |
||||
if err != nil { |
||||
t.Fatal("Failed to create db:", err) |
||||
} |
||||
// make first chain starting from genesis
|
||||
bman, err := newCanonical(10, db) |
||||
if err != nil { |
||||
t.Fatal("Could not make new canonical chain:", err) |
||||
} |
||||
f := func(td1, td2 *big.Int) { |
||||
if td2.Cmp(td1) <= 0 { |
||||
t.Error("expected chainB to have higher difficulty. Got", td2, "expected more than", td1) |
||||
} |
||||
} |
||||
// Sum of numbers must be greater than 10
|
||||
// for this to be a longer fork
|
||||
testFork(t, bman, 0, 11, f) |
||||
testFork(t, bman, 0, 15, f) |
||||
testFork(t, bman, 1, 10, f) |
||||
testFork(t, bman, 1, 12, f) |
||||
testFork(t, bman, 5, 6, f) |
||||
testFork(t, bman, 5, 8, f) |
||||
} |
||||
|
||||
func TestEqualFork(t *testing.T) { |
||||
db, err := ethdb.NewMemDatabase() |
||||
if err != nil { |
||||
t.Fatal("Failed to create db:", err) |
||||
} |
||||
bman, err := newCanonical(10, db) |
||||
if err != nil { |
||||
t.Fatal("Could not make new canonical chain:", err) |
||||
} |
||||
f := func(td1, td2 *big.Int) { |
||||
if td2.Cmp(td1) != 0 { |
||||
t.Error("expected chainB to have equal difficulty. Got", td2, "expected ", td1) |
||||
} |
||||
} |
||||
// Sum of numbers must be equal to 10
|
||||
// for this to be an equal fork
|
||||
testFork(t, bman, 0, 10, f) |
||||
testFork(t, bman, 1, 9, f) |
||||
testFork(t, bman, 2, 8, f) |
||||
testFork(t, bman, 5, 5, f) |
||||
testFork(t, bman, 6, 4, f) |
||||
testFork(t, bman, 9, 1, f) |
||||
} |
||||
|
||||
func TestBrokenChain(t *testing.T) { |
||||
db, err := ethdb.NewMemDatabase() |
||||
if err != nil { |
||||
t.Fatal("Failed to create db:", err) |
||||
} |
||||
bman, err := newCanonical(10, db) |
||||
if err != nil { |
||||
t.Fatal("Could not make new canonical chain:", err) |
||||
} |
||||
db2, err := ethdb.NewMemDatabase() |
||||
if err != nil { |
||||
t.Fatal("Failed to create db:", err) |
||||
} |
||||
bman2, err := newCanonical(10, db2) |
||||
if err != nil { |
||||
t.Fatal("Could not make new canonical chain:", err) |
||||
} |
||||
bman2.bc.SetProcessor(bman2) |
||||
parent := bman2.bc.CurrentBlock() |
||||
chainB := makeChain(parent, 5, db2, forkSeed) |
||||
chainB = chainB[1:] |
||||
_, err = testChain(chainB, bman) |
||||
if err == nil { |
||||
t.Error("expected broken chain to return error") |
||||
} |
||||
} |
||||
|
||||
func TestChainInsertions(t *testing.T) { |
||||
t.Skip("Skipped: outdated test files") |
||||
|
||||
db, _ := ethdb.NewMemDatabase() |
||||
|
||||
chain1, err := loadChain("valid1", t) |
||||
if err != nil { |
||||
fmt.Println(err) |
||||
t.FailNow() |
||||
} |
||||
|
||||
chain2, err := loadChain("valid2", t) |
||||
if err != nil { |
||||
fmt.Println(err) |
||||
t.FailNow() |
||||
} |
||||
|
||||
chainMan := theChainManager(db, t) |
||||
|
||||
const max = 2 |
||||
done := make(chan bool, max) |
||||
|
||||
go insertChain(done, chainMan, chain1, t) |
||||
go insertChain(done, chainMan, chain2, t) |
||||
|
||||
for i := 0; i < max; i++ { |
||||
<-done |
||||
} |
||||
|
||||
if chain2[len(chain2)-1].Hash() != chainMan.CurrentBlock().Hash() { |
||||
t.Error("chain2 is canonical and shouldn't be") |
||||
} |
||||
|
||||
if chain1[len(chain1)-1].Hash() != chainMan.CurrentBlock().Hash() { |
||||
t.Error("chain1 isn't canonical and should be") |
||||
} |
||||
} |
||||
|
||||
func TestChainMultipleInsertions(t *testing.T) { |
||||
t.Skip("Skipped: outdated test files") |
||||
|
||||
db, _ := ethdb.NewMemDatabase() |
||||
|
||||
const max = 4 |
||||
chains := make([]types.Blocks, max) |
||||
var longest int |
||||
for i := 0; i < max; i++ { |
||||
var err error |
||||
name := "valid" + strconv.Itoa(i+1) |
||||
chains[i], err = loadChain(name, t) |
||||
if len(chains[i]) >= len(chains[longest]) { |
||||
longest = i |
||||
} |
||||
fmt.Println("loaded", name, "with a length of", len(chains[i])) |
||||
if err != nil { |
||||
fmt.Println(err) |
||||
t.FailNow() |
||||
} |
||||
} |
||||
|
||||
chainMan := theChainManager(db, t) |
||||
|
||||
done := make(chan bool, max) |
||||
for i, chain := range chains { |
||||
// XXX the go routine would otherwise reference the same (chain[3]) variable and fail
|
||||
i := i |
||||
chain := chain |
||||
go func() { |
||||
insertChain(done, chainMan, chain, t) |
||||
fmt.Println(i, "done") |
||||
}() |
||||
} |
||||
|
||||
for i := 0; i < max; i++ { |
||||
<-done |
||||
} |
||||
|
||||
if chains[longest][len(chains[longest])-1].Hash() != chainMan.CurrentBlock().Hash() { |
||||
t.Error("Invalid canonical chain") |
||||
} |
||||
} |
||||
|
||||
type bproc struct{} |
||||
|
||||
func (bproc) Process(*types.Block) (state.Logs, types.Receipts, error) { return nil, nil, nil } |
||||
|
||||
func makeChainWithDiff(genesis *types.Block, d []int, seed byte) []*types.Block { |
||||
var chain []*types.Block |
||||
for i, difficulty := range d { |
||||
header := &types.Header{ |
||||
Coinbase: common.Address{seed}, |
||||
Number: big.NewInt(int64(i + 1)), |
||||
Difficulty: big.NewInt(int64(difficulty)), |
||||
} |
||||
if i == 0 { |
||||
header.ParentHash = genesis.Hash() |
||||
} else { |
||||
header.ParentHash = chain[i-1].Hash() |
||||
} |
||||
block := types.NewBlockWithHeader(header) |
||||
chain = append(chain, block) |
||||
} |
||||
return chain |
||||
} |
||||
|
||||
func chm(genesis *types.Block, db ethdb.Database) *ChainManager { |
||||
var eventMux event.TypeMux |
||||
bc := &ChainManager{chainDb: db, genesisBlock: genesis, eventMux: &eventMux, pow: FakePow{}} |
||||
bc.headerCache, _ = lru.New(100) |
||||
bc.bodyCache, _ = lru.New(100) |
||||
bc.bodyRLPCache, _ = lru.New(100) |
||||
bc.tdCache, _ = lru.New(100) |
||||
bc.blockCache, _ = lru.New(100) |
||||
bc.futureBlocks, _ = lru.New(100) |
||||
bc.processor = bproc{} |
||||
bc.ResetWithGenesisBlock(genesis) |
||||
|
||||
return bc |
||||
} |
||||
|
||||
func TestReorgLongest(t *testing.T) { |
||||
db, _ := ethdb.NewMemDatabase() |
||||
|
||||
genesis, err := WriteTestNetGenesisBlock(db, 0) |
||||
if err != nil { |
||||
t.Error(err) |
||||
t.FailNow() |
||||
} |
||||
bc := chm(genesis, db) |
||||
|
||||
chain1 := makeChainWithDiff(genesis, []int{1, 2, 4}, 10) |
||||
chain2 := makeChainWithDiff(genesis, []int{1, 2, 3, 4}, 11) |
||||
|
||||
bc.InsertChain(chain1) |
||||
bc.InsertChain(chain2) |
||||
|
||||
prev := bc.CurrentBlock() |
||||
for block := bc.GetBlockByNumber(bc.CurrentBlock().NumberU64() - 1); block.NumberU64() != 0; prev, block = block, bc.GetBlockByNumber(block.NumberU64()-1) { |
||||
if prev.ParentHash() != block.Hash() { |
||||
t.Errorf("parent hash mismatch %x - %x", prev.ParentHash(), block.Hash()) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestBadHashes(t *testing.T) { |
||||
db, _ := ethdb.NewMemDatabase() |
||||
genesis, err := WriteTestNetGenesisBlock(db, 0) |
||||
if err != nil { |
||||
t.Error(err) |
||||
t.FailNow() |
||||
} |
||||
bc := chm(genesis, db) |
||||
|
||||
chain := makeChainWithDiff(genesis, []int{1, 2, 4}, 10) |
||||
BadHashes[chain[2].Header().Hash()] = true |
||||
|
||||
_, err = bc.InsertChain(chain) |
||||
if !IsBadHashError(err) { |
||||
t.Errorf("error mismatch: want: BadHashError, have: %v", err) |
||||
} |
||||
} |
||||
|
||||
func TestReorgBadHashes(t *testing.T) { |
||||
db, _ := ethdb.NewMemDatabase() |
||||
genesis, err := WriteTestNetGenesisBlock(db, 0) |
||||
if err != nil { |
||||
t.Error(err) |
||||
t.FailNow() |
||||
} |
||||
bc := chm(genesis, db) |
||||
|
||||
chain := makeChainWithDiff(genesis, []int{1, 2, 3, 4}, 11) |
||||
bc.InsertChain(chain) |
||||
|
||||
if chain[3].Header().Hash() != bc.LastBlockHash() { |
||||
t.Errorf("last block hash mismatch: want: %x, have: %x", chain[3].Header().Hash(), bc.LastBlockHash()) |
||||
} |
||||
|
||||
// NewChainManager should check BadHashes when loading it db
|
||||
BadHashes[chain[3].Header().Hash()] = true |
||||
|
||||
var eventMux event.TypeMux |
||||
ncm, err := NewChainManager(db, FakePow{}, &eventMux) |
||||
if err != nil { |
||||
t.Errorf("NewChainManager err: %s", err) |
||||
} |
||||
|
||||
// check it set head to (valid) parent of bad hash block
|
||||
if chain[2].Header().Hash() != ncm.LastBlockHash() { |
||||
t.Errorf("last block hash mismatch: want: %x, have: %x", chain[2].Header().Hash(), ncm.LastBlockHash()) |
||||
} |
||||
|
||||
if chain[2].Header().GasLimit.Cmp(ncm.GasLimit()) != 0 { |
||||
t.Errorf("current block gasLimit mismatch: want: %x, have: %x", chain[2].Header().GasLimit, ncm.GasLimit()) |
||||
} |
||||
} |
||||
|
||||
func TestReorgShortest(t *testing.T) { |
||||
db, _ := ethdb.NewMemDatabase() |
||||
genesis, err := WriteTestNetGenesisBlock(db, 0) |
||||
if err != nil { |
||||
t.Error(err) |
||||
t.FailNow() |
||||
} |
||||
bc := chm(genesis, db) |
||||
|
||||
chain1 := makeChainWithDiff(genesis, []int{1, 2, 3, 4}, 10) |
||||
chain2 := makeChainWithDiff(genesis, []int{1, 10}, 11) |
||||
|
||||
bc.InsertChain(chain1) |
||||
bc.InsertChain(chain2) |
||||
|
||||
prev := bc.CurrentBlock() |
||||
for block := bc.GetBlockByNumber(bc.CurrentBlock().NumberU64() - 1); block.NumberU64() != 0; prev, block = block, bc.GetBlockByNumber(block.NumberU64()-1) { |
||||
if prev.ParentHash() != block.Hash() { |
||||
t.Errorf("parent hash mismatch %x - %x", prev.ParentHash(), block.Hash()) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestInsertNonceError(t *testing.T) { |
||||
for i := 1; i < 25 && !t.Failed(); i++ { |
||||
db, _ := ethdb.NewMemDatabase() |
||||
genesis, err := WriteTestNetGenesisBlock(db, 0) |
||||
if err != nil { |
||||
t.Error(err) |
||||
t.FailNow() |
||||
} |
||||
bc := chm(genesis, db) |
||||
bc.processor = NewBlockProcessor(db, bc.pow, bc, bc.eventMux) |
||||
blocks := makeChain(bc.currentBlock, i, db, 0) |
||||
|
||||
fail := rand.Int() % len(blocks) |
||||
failblock := blocks[fail] |
||||
bc.pow = failPow{failblock.NumberU64()} |
||||
n, err := bc.InsertChain(blocks) |
||||
|
||||
// Check that the returned error indicates the nonce failure.
|
||||
if n != fail { |
||||
t.Errorf("(i=%d) wrong failed block index: got %d, want %d", i, n, fail) |
||||
} |
||||
if !IsBlockNonceErr(err) { |
||||
t.Fatalf("(i=%d) got %q, want a nonce error", i, err) |
||||
} |
||||
nerr := err.(*BlockNonceErr) |
||||
if nerr.Number.Cmp(failblock.Number()) != 0 { |
||||
t.Errorf("(i=%d) wrong block number in error, got %v, want %v", i, nerr.Number, failblock.Number()) |
||||
} |
||||
if nerr.Hash != failblock.Hash() { |
||||
t.Errorf("(i=%d) wrong block hash in error, got %v, want %v", i, nerr.Hash, failblock.Hash()) |
||||
} |
||||
|
||||
// Check that all no blocks after the failing block have been inserted.
|
||||
for _, block := range blocks[fail:] { |
||||
if bc.HasBlock(block.Hash()) { |
||||
t.Errorf("(i=%d) invalid block %d present in chain", i, block.NumberU64()) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Tests that chain reorganizations handle transaction removals and reinsertions.
|
||||
func TestChainTxReorgs(t *testing.T) { |
||||
params.MinGasLimit = big.NewInt(125000) // Minimum the gas limit may ever be.
|
||||
params.GenesisGasLimit = big.NewInt(3141592) // Gas limit of the Genesis block.
|
||||
|
||||
var ( |
||||
key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") |
||||
key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") |
||||
key3, _ = crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee") |
||||
addr1 = crypto.PubkeyToAddress(key1.PublicKey) |
||||
addr2 = crypto.PubkeyToAddress(key2.PublicKey) |
||||
addr3 = crypto.PubkeyToAddress(key3.PublicKey) |
||||
db, _ = ethdb.NewMemDatabase() |
||||
) |
||||
genesis := WriteGenesisBlockForTesting(db, |
||||
GenesisAccount{addr1, big.NewInt(1000000)}, |
||||
GenesisAccount{addr2, big.NewInt(1000000)}, |
||||
GenesisAccount{addr3, big.NewInt(1000000)}, |
||||
) |
||||
// Create two transactions shared between the chains:
|
||||
// - postponed: transaction included at a later block in the forked chain
|
||||
// - swapped: transaction included at the same block number in the forked chain
|
||||
postponed, _ := types.NewTransaction(0, addr1, big.NewInt(1000), params.TxGas, nil, nil).SignECDSA(key1) |
||||
swapped, _ := types.NewTransaction(1, addr1, big.NewInt(1000), params.TxGas, nil, nil).SignECDSA(key1) |
||||
|
||||
// Create two transactions that will be dropped by the forked chain:
|
||||
// - pastDrop: transaction dropped retroactively from a past block
|
||||
// - freshDrop: transaction dropped exactly at the block where the reorg is detected
|
||||
var pastDrop, freshDrop *types.Transaction |
||||
|
||||
// Create three transactions that will be added in the forked chain:
|
||||
// - pastAdd: transaction added before the reorganiztion is detected
|
||||
// - freshAdd: transaction added at the exact block the reorg is detected
|
||||
// - futureAdd: transaction added after the reorg has already finished
|
||||
var pastAdd, freshAdd, futureAdd *types.Transaction |
||||
|
||||
chain := GenerateChain(genesis, db, 3, func(i int, gen *BlockGen) { |
||||
switch i { |
||||
case 0: |
||||
pastDrop, _ = types.NewTransaction(gen.TxNonce(addr2), addr2, big.NewInt(1000), params.TxGas, nil, nil).SignECDSA(key2) |
||||
|
||||
gen.AddTx(pastDrop) // This transaction will be dropped in the fork from below the split point
|
||||
gen.AddTx(postponed) // This transaction will be postponed till block #3 in the fork
|
||||
|
||||
case 2: |
||||
freshDrop, _ = types.NewTransaction(gen.TxNonce(addr2), addr2, big.NewInt(1000), params.TxGas, nil, nil).SignECDSA(key2) |
||||
|
||||
gen.AddTx(freshDrop) // This transaction will be dropped in the fork from exactly at the split point
|
||||
gen.AddTx(swapped) // This transaction will be swapped out at the exact height
|
||||
|
||||
gen.OffsetTime(9) // Lower the block difficulty to simulate a weaker chain
|
||||
} |
||||
}) |
||||
// Import the chain. This runs all block validation rules.
|
||||
evmux := &event.TypeMux{} |
||||
chainman, _ := NewChainManager(db, FakePow{}, evmux) |
||||
chainman.SetProcessor(NewBlockProcessor(db, FakePow{}, chainman, evmux)) |
||||
if i, err := chainman.InsertChain(chain); err != nil { |
||||
t.Fatalf("failed to insert original chain[%d]: %v", i, err) |
||||
} |
||||
|
||||
// overwrite the old chain
|
||||
chain = GenerateChain(genesis, db, 5, func(i int, gen *BlockGen) { |
||||
switch i { |
||||
case 0: |
||||
pastAdd, _ = types.NewTransaction(gen.TxNonce(addr3), addr3, big.NewInt(1000), params.TxGas, nil, nil).SignECDSA(key3) |
||||
gen.AddTx(pastAdd) // This transaction needs to be injected during reorg
|
||||
|
||||
case 2: |
||||
gen.AddTx(postponed) // This transaction was postponed from block #1 in the original chain
|
||||
gen.AddTx(swapped) // This transaction was swapped from the exact current spot in the original chain
|
||||
|
||||
freshAdd, _ = types.NewTransaction(gen.TxNonce(addr3), addr3, big.NewInt(1000), params.TxGas, nil, nil).SignECDSA(key3) |
||||
gen.AddTx(freshAdd) // This transaction will be added exactly at reorg time
|
||||
|
||||
case 3: |
||||
futureAdd, _ = types.NewTransaction(gen.TxNonce(addr3), addr3, big.NewInt(1000), params.TxGas, nil, nil).SignECDSA(key3) |
||||
gen.AddTx(futureAdd) // This transaction will be added after a full reorg
|
||||
} |
||||
}) |
||||
if _, err := chainman.InsertChain(chain); err != nil { |
||||
t.Fatalf("failed to insert forked chain: %v", err) |
||||
} |
||||
|
||||
// removed tx
|
||||
for i, tx := range (types.Transactions{pastDrop, freshDrop}) { |
||||
if GetTransaction(db, tx.Hash()) != nil { |
||||
t.Errorf("drop %d: tx found while shouldn't have been", i) |
||||
} |
||||
if GetReceipt(db, tx.Hash()) != nil { |
||||
t.Errorf("drop %d: receipt found while shouldn't have been", i) |
||||
} |
||||
} |
||||
// added tx
|
||||
for i, tx := range (types.Transactions{pastAdd, freshAdd, futureAdd}) { |
||||
if GetTransaction(db, tx.Hash()) == nil { |
||||
t.Errorf("add %d: expected tx to be found", i) |
||||
} |
||||
if GetReceipt(db, tx.Hash()) == nil { |
||||
t.Errorf("add %d: expected receipt to be found", i) |
||||
} |
||||
} |
||||
// shared tx
|
||||
for i, tx := range (types.Transactions{postponed, swapped}) { |
||||
if GetTransaction(db, tx.Hash()) == nil { |
||||
t.Errorf("share %d: expected tx to be found", i) |
||||
} |
||||
if GetReceipt(db, tx.Hash()) == nil { |
||||
t.Errorf("share %d: expected receipt to be found", i) |
||||
} |
||||
} |
||||
} |
@ -1,212 +0,0 @@ |
||||
// Copyright 2014 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 core |
||||
|
||||
import ( |
||||
"math" |
||||
|
||||
"github.com/ethereum/go-ethereum/common" |
||||
"github.com/ethereum/go-ethereum/core/state" |
||||
"github.com/ethereum/go-ethereum/core/types" |
||||
"github.com/ethereum/go-ethereum/logger" |
||||
"github.com/ethereum/go-ethereum/logger/glog" |
||||
) |
||||
|
||||
type AccountChange struct { |
||||
Address, StateAddress []byte |
||||
} |
||||
|
||||
// Filtering interface
|
||||
type Filter struct { |
||||
eth Backend |
||||
earliest int64 |
||||
latest int64 |
||||
skip int |
||||
address []common.Address |
||||
max int |
||||
topics [][]common.Hash |
||||
|
||||
BlockCallback func(*types.Block, state.Logs) |
||||
TransactionCallback func(*types.Transaction) |
||||
LogsCallback func(state.Logs) |
||||
} |
||||
|
||||
// Create a new filter which uses a bloom filter on blocks to figure out whether a particular block
|
||||
// is interesting or not.
|
||||
func NewFilter(eth Backend) *Filter { |
||||
return &Filter{eth: eth} |
||||
} |
||||
|
||||
// Set the earliest and latest block for filtering.
|
||||
// -1 = latest block (i.e., the current block)
|
||||
// hash = particular hash from-to
|
||||
func (self *Filter) SetEarliestBlock(earliest int64) { |
||||
self.earliest = earliest |
||||
} |
||||
|
||||
func (self *Filter) SetLatestBlock(latest int64) { |
||||
self.latest = latest |
||||
} |
||||
|
||||
func (self *Filter) SetAddress(addr []common.Address) { |
||||
self.address = addr |
||||
} |
||||
|
||||
func (self *Filter) SetTopics(topics [][]common.Hash) { |
||||
self.topics = topics |
||||
} |
||||
|
||||
func (self *Filter) SetMax(max int) { |
||||
self.max = max |
||||
} |
||||
|
||||
func (self *Filter) SetSkip(skip int) { |
||||
self.skip = skip |
||||
} |
||||
|
||||
// Run filters logs with the current parameters set
|
||||
func (self *Filter) Find() state.Logs { |
||||
earliestBlock := self.eth.ChainManager().CurrentBlock() |
||||
var earliestBlockNo uint64 = uint64(self.earliest) |
||||
if self.earliest == -1 { |
||||
earliestBlockNo = earliestBlock.NumberU64() |
||||
} |
||||
var latestBlockNo uint64 = uint64(self.latest) |
||||
if self.latest == -1 { |
||||
latestBlockNo = earliestBlock.NumberU64() |
||||
} |
||||
|
||||
var ( |
||||
logs state.Logs |
||||
block = self.eth.ChainManager().GetBlockByNumber(latestBlockNo) |
||||
) |
||||
|
||||
done: |
||||
for i := 0; block != nil; i++ { |
||||
// Quit on latest
|
||||
switch { |
||||
case block.NumberU64() == 0: |
||||
break done |
||||
case block.NumberU64() < earliestBlockNo: |
||||
break done |
||||
case self.max <= len(logs): |
||||
break done |
||||
} |
||||
|
||||
// Use bloom filtering to see if this block is interesting given the
|
||||
// current parameters
|
||||
if self.bloomFilter(block) { |
||||
// Get the logs of the block
|
||||
unfiltered, err := self.eth.BlockProcessor().GetLogs(block) |
||||
if err != nil { |
||||
glog.V(logger.Warn).Infoln("err: filter get logs ", err) |
||||
|
||||
break |
||||
} |
||||
|
||||
logs = append(logs, self.FilterLogs(unfiltered)...) |
||||
} |
||||
|
||||
block = self.eth.ChainManager().GetBlock(block.ParentHash()) |
||||
} |
||||
|
||||
skip := int(math.Min(float64(len(logs)), float64(self.skip))) |
||||
|
||||
return logs[skip:] |
||||
} |
||||
|
||||
func includes(addresses []common.Address, a common.Address) bool { |
||||
for _, addr := range addresses { |
||||
if addr == a { |
||||
return true |
||||
} |
||||
} |
||||
|
||||
return false |
||||
} |
||||
|
||||
func (self *Filter) FilterLogs(logs state.Logs) state.Logs { |
||||
var ret state.Logs |
||||
|
||||
// Filter the logs for interesting stuff
|
||||
Logs: |
||||
for _, log := range logs { |
||||
if len(self.address) > 0 && !includes(self.address, log.Address) { |
||||
continue |
||||
} |
||||
|
||||
logTopics := make([]common.Hash, len(self.topics)) |
||||
copy(logTopics, log.Topics) |
||||
|
||||
// If the to filtered topics is greater than the amount of topics in
|
||||
// logs, skip.
|
||||
if len(self.topics) > len(log.Topics) { |
||||
continue Logs |
||||
} |
||||
|
||||
for i, topics := range self.topics { |
||||
var match bool |
||||
for _, topic := range topics { |
||||
// common.Hash{} is a match all (wildcard)
|
||||
if (topic == common.Hash{}) || log.Topics[i] == topic { |
||||
match = true |
||||
break |
||||
} |
||||
} |
||||
|
||||
if !match { |
||||
continue Logs |
||||
} |
||||
|
||||
} |
||||
|
||||
ret = append(ret, log) |
||||
} |
||||
|
||||
return ret |
||||
} |
||||
|
||||
func (self *Filter) bloomFilter(block *types.Block) bool { |
||||
if len(self.address) > 0 { |
||||
var included bool |
||||
for _, addr := range self.address { |
||||
if types.BloomLookup(block.Bloom(), addr) { |
||||
included = true |
||||
break |
||||
} |
||||
} |
||||
|
||||
if !included { |
||||
return false |
||||
} |
||||
} |
||||
|
||||
for _, sub := range self.topics { |
||||
var included bool |
||||
for _, topic := range sub { |
||||
if (topic == common.Hash{}) || types.BloomLookup(block.Bloom(), topic) { |
||||
included = true |
||||
break |
||||
} |
||||
} |
||||
if !included { |
||||
return false |
||||
} |
||||
} |
||||
|
||||
return true |
||||
} |
@ -1,39 +0,0 @@ |
||||
// Copyright 2014 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 state |
||||
|
||||
import ( |
||||
"fmt" |
||||
"math/big" |
||||
) |
||||
|
||||
type GasLimitErr struct { |
||||
Message string |
||||
Is, Max *big.Int |
||||
} |
||||
|
||||
func IsGasLimitErr(err error) bool { |
||||
_, ok := err.(*GasLimitErr) |
||||
|
||||
return ok |
||||
} |
||||
func (err *GasLimitErr) Error() string { |
||||
return err.Message |
||||
} |
||||
func GasLimitError(is, max *big.Int) *GasLimitErr { |
||||
return &GasLimitErr{Message: fmt.Sprintf("GasLimit error. Max %s, transaction would take it to %s", max, is), Is: is, Max: max} |
||||
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue